264 lines
7.7 KiB
TypeScript
264 lines
7.7 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Delete,
|
|
Body,
|
|
Param,
|
|
UseGuards,
|
|
Request,
|
|
HttpCode,
|
|
HttpStatus,
|
|
} from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
|
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
|
import { TenantIntegrationsService } from '../services/tenant-integrations.service';
|
|
import {
|
|
UpsertWhatsAppCredentialsDto,
|
|
UpsertLLMCredentialsDto,
|
|
CreateIntegrationCredentialDto,
|
|
IntegrationCredentialResponseDto,
|
|
IntegrationStatusResponseDto,
|
|
} from '../dto/integration-credentials.dto';
|
|
import {
|
|
IntegrationType,
|
|
IntegrationProvider,
|
|
} from '../entities/tenant-integration-credential.entity';
|
|
|
|
@ApiTags('Integrations')
|
|
@ApiBearerAuth()
|
|
@UseGuards(JwtAuthGuard)
|
|
@Controller('integrations')
|
|
export class IntegrationsController {
|
|
constructor(private readonly integrationsService: TenantIntegrationsService) {}
|
|
|
|
// =========================================================================
|
|
// STATUS
|
|
// =========================================================================
|
|
|
|
@Get('status')
|
|
@ApiOperation({ summary: 'Obtener estado de todas las integraciones del tenant' })
|
|
@ApiResponse({ status: 200, type: IntegrationStatusResponseDto })
|
|
async getStatus(@Request() req): Promise<IntegrationStatusResponseDto> {
|
|
return this.integrationsService.getIntegrationStatus(req.user.tenantId);
|
|
}
|
|
|
|
// =========================================================================
|
|
// WHATSAPP
|
|
// =========================================================================
|
|
|
|
@Get('whatsapp')
|
|
@ApiOperation({ summary: 'Obtener configuración de WhatsApp' })
|
|
async getWhatsAppConfig(@Request() req) {
|
|
const credential = await this.integrationsService.getCredential(
|
|
req.user.tenantId,
|
|
IntegrationType.WHATSAPP,
|
|
IntegrationProvider.META,
|
|
);
|
|
|
|
if (!credential) {
|
|
return {
|
|
configured: false,
|
|
usesPlatformNumber: true,
|
|
message: 'Usando número de plataforma compartido',
|
|
};
|
|
}
|
|
|
|
return {
|
|
configured: true,
|
|
usesPlatformNumber: false,
|
|
isVerified: credential.isVerified,
|
|
lastVerifiedAt: credential.lastVerifiedAt,
|
|
// No exponer credenciales sensibles
|
|
hasAccessToken: !!credential.credentials?.['accessToken'],
|
|
phoneNumberId: credential.credentials?.['phoneNumberId'],
|
|
};
|
|
}
|
|
|
|
@Put('whatsapp')
|
|
@ApiOperation({ summary: 'Configurar credenciales de WhatsApp propias' })
|
|
async upsertWhatsAppCredentials(
|
|
@Request() req,
|
|
@Body() dto: UpsertWhatsAppCredentialsDto,
|
|
) {
|
|
const credential = await this.integrationsService.upsertCredential(
|
|
req.user.tenantId,
|
|
IntegrationType.WHATSAPP,
|
|
IntegrationProvider.META,
|
|
{
|
|
accessToken: dto.credentials.accessToken,
|
|
phoneNumberId: dto.credentials.phoneNumberId,
|
|
businessAccountId: dto.credentials.businessAccountId,
|
|
verifyToken: dto.credentials.verifyToken,
|
|
},
|
|
{},
|
|
req.user.sub,
|
|
);
|
|
|
|
// Registrar el número de WhatsApp para resolución en webhooks
|
|
if (dto.credentials.phoneNumberId) {
|
|
await this.integrationsService.registerWhatsAppNumber(
|
|
req.user.tenantId,
|
|
dto.credentials.phoneNumberId,
|
|
dto.phoneNumber,
|
|
dto.displayName,
|
|
);
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Credenciales de WhatsApp configuradas',
|
|
id: credential.id,
|
|
};
|
|
}
|
|
|
|
@Delete('whatsapp')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
@ApiOperation({ summary: 'Eliminar credenciales de WhatsApp (volver a usar plataforma)' })
|
|
async deleteWhatsAppCredentials(@Request() req) {
|
|
await this.integrationsService.deleteCredential(
|
|
req.user.tenantId,
|
|
IntegrationType.WHATSAPP,
|
|
IntegrationProvider.META,
|
|
);
|
|
}
|
|
|
|
// =========================================================================
|
|
// LLM
|
|
// =========================================================================
|
|
|
|
@Get('llm')
|
|
@ApiOperation({ summary: 'Obtener configuración de LLM' })
|
|
async getLLMConfig(@Request() req) {
|
|
const credentials = await this.integrationsService.getCredentials(req.user.tenantId);
|
|
const llmCred = credentials.find(
|
|
(c) => c.integrationType === IntegrationType.LLM && c.isActive,
|
|
);
|
|
|
|
if (!llmCred) {
|
|
return {
|
|
configured: false,
|
|
usesPlatformDefault: true,
|
|
message: 'Usando configuración LLM de plataforma',
|
|
};
|
|
}
|
|
|
|
return {
|
|
configured: true,
|
|
usesPlatformDefault: false,
|
|
provider: llmCred.provider,
|
|
isVerified: llmCred.isVerified,
|
|
config: {
|
|
model: llmCred.config?.['model'],
|
|
maxTokens: llmCred.config?.['maxTokens'],
|
|
temperature: llmCred.config?.['temperature'],
|
|
hasSystemPrompt: !!llmCred.config?.['systemPrompt'],
|
|
},
|
|
// No exponer API key
|
|
hasApiKey: !!llmCred.credentials?.['apiKey'],
|
|
};
|
|
}
|
|
|
|
@Put('llm')
|
|
@ApiOperation({ summary: 'Configurar credenciales de LLM propias' })
|
|
async upsertLLMCredentials(@Request() req, @Body() dto: UpsertLLMCredentialsDto) {
|
|
const credential = await this.integrationsService.upsertCredential(
|
|
req.user.tenantId,
|
|
IntegrationType.LLM,
|
|
dto.provider,
|
|
{ apiKey: dto.credentials.apiKey },
|
|
dto.config || {},
|
|
req.user.sub,
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Credenciales de LLM configuradas',
|
|
id: credential.id,
|
|
provider: dto.provider,
|
|
};
|
|
}
|
|
|
|
@Delete('llm/:provider')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
@ApiOperation({ summary: 'Eliminar credenciales de LLM (volver a usar plataforma)' })
|
|
async deleteLLMCredentials(
|
|
@Request() req,
|
|
@Param('provider') provider: IntegrationProvider,
|
|
) {
|
|
await this.integrationsService.deleteCredential(
|
|
req.user.tenantId,
|
|
IntegrationType.LLM,
|
|
provider,
|
|
);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GENERIC CRUD
|
|
// =========================================================================
|
|
|
|
@Get('credentials')
|
|
@ApiOperation({ summary: 'Obtener todas las credenciales del tenant' })
|
|
async getAllCredentials(@Request() req): Promise<IntegrationCredentialResponseDto[]> {
|
|
const credentials = await this.integrationsService.getCredentials(req.user.tenantId);
|
|
|
|
return credentials.map((c) => ({
|
|
id: c.id,
|
|
integrationType: c.integrationType,
|
|
provider: c.provider,
|
|
hasCredentials: !!c.credentials && Object.keys(c.credentials).length > 0,
|
|
isActive: c.isActive,
|
|
isVerified: c.isVerified,
|
|
lastVerifiedAt: c.lastVerifiedAt,
|
|
verificationError: c.verificationError,
|
|
config: c.config,
|
|
createdAt: c.createdAt,
|
|
updatedAt: c.updatedAt,
|
|
}));
|
|
}
|
|
|
|
@Post('credentials')
|
|
@ApiOperation({ summary: 'Crear credencial de integración genérica' })
|
|
async createCredential(
|
|
@Request() req,
|
|
@Body() dto: CreateIntegrationCredentialDto,
|
|
) {
|
|
const credential = await this.integrationsService.upsertCredential(
|
|
req.user.tenantId,
|
|
dto.integrationType,
|
|
dto.provider,
|
|
dto.credentials,
|
|
dto.config,
|
|
req.user.sub,
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Credencial creada',
|
|
id: credential.id,
|
|
};
|
|
}
|
|
|
|
@Put('credentials/:type/:provider/toggle')
|
|
@ApiOperation({ summary: 'Activar/desactivar una credencial' })
|
|
async toggleCredential(
|
|
@Request() req,
|
|
@Param('type') type: IntegrationType,
|
|
@Param('provider') provider: IntegrationProvider,
|
|
@Body() body: { isActive: boolean },
|
|
) {
|
|
const credential = await this.integrationsService.toggleCredential(
|
|
req.user.tenantId,
|
|
type,
|
|
provider,
|
|
body.isActive,
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
isActive: credential.isActive,
|
|
};
|
|
}
|
|
}
|