import { Controller, Get, Put, Body, UseGuards, Req, } from '@nestjs/common'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { TenantDatabaseService } from './tenant-database.service'; import { getCentralPrisma } from '../prisma/central-prisma.service'; import { TenantId } from './tenant.decorator'; @Controller('tenant') @UseGuards(JwtAuthGuard) export class TenantController { constructor(private readonly tenantDbService: TenantDatabaseService) {} /** * Helper to find tenant by ID or domain */ private async findTenant(identifier: string) { const centralPrisma = getCentralPrisma(); // Check if identifier is a CUID (tenant ID) or a domain const isCUID = /^c[a-z0-9]{24}$/i.test(identifier); if (isCUID) { // Look up by tenant ID directly return centralPrisma.tenant.findUnique({ where: { id: identifier }, select: { id: true, integrationsConfig: true }, }); } else { // Look up by domain const domainRecord = await centralPrisma.domain.findUnique({ where: { domain: identifier }, include: { tenant: { select: { id: true, integrationsConfig: true } } }, }); return domainRecord?.tenant; } } /** * Get integrations configuration for the current tenant */ @Get('integrations') async getIntegrationsConfig(@TenantId() tenantIdentifier: string) { const tenant = await this.findTenant(tenantIdentifier); if (!tenant || !tenant.integrationsConfig) { return { data: null }; } // Decrypt the config const config = this.tenantDbService.decryptIntegrationsConfig( tenant.integrationsConfig as any, ); // Return config with sensitive fields masked const maskedConfig = this.maskSensitiveFields(config); return { data: maskedConfig }; } /** * Update integrations configuration for the current tenant */ @Put('integrations') async updateIntegrationsConfig( @TenantId() tenantIdentifier: string, @Body() body: { integrationsConfig: any }, ) { const { integrationsConfig } = body; if (!tenantIdentifier) { throw new Error('Tenant identifier is missing from request'); } const tenant = await this.findTenant(tenantIdentifier); if (!tenant) { throw new Error(`Tenant with identifier ${tenantIdentifier} not found`); } // Merge with existing config to preserve masked values let finalConfig = integrationsConfig; if (tenant.integrationsConfig) { const existingConfig = this.tenantDbService.decryptIntegrationsConfig( tenant.integrationsConfig as any, ); // Replace masked values with actual values from existing config finalConfig = this.unmaskConfig(integrationsConfig, existingConfig); } // Encrypt the config const encryptedConfig = this.tenantDbService.encryptIntegrationsConfig( finalConfig, ); // Update in database const centralPrisma = getCentralPrisma(); await centralPrisma.tenant.update({ where: { id: tenant.id }, data: { integrationsConfig: encryptedConfig as any, }, }); return { success: true, message: 'Integrations configuration updated successfully', }; } /** * Unmask config by replacing masked values with actual values from existing config */ private unmaskConfig(newConfig: any, existingConfig: any): any { const result = { ...newConfig }; // Unmask Twilio credentials if (result.twilio && existingConfig.twilio) { if (result.twilio.authToken === '••••••••' && existingConfig.twilio.authToken) { result.twilio.authToken = existingConfig.twilio.authToken; } if (result.twilio.apiSecret === '••••••••' && existingConfig.twilio.apiSecret) { result.twilio.apiSecret = existingConfig.twilio.apiSecret; } } // Unmask OpenAI credentials if (result.openai && existingConfig.openai) { if (result.openai.apiKey === '••••••••' && existingConfig.openai.apiKey) { result.openai.apiKey = existingConfig.openai.apiKey; } } return result; } /** * Mask sensitive fields for API responses */ private maskSensitiveFields(config: any): any { if (!config) return null; const masked = { ...config }; // Mask Twilio credentials if (masked.twilio) { masked.twilio = { ...masked.twilio, authToken: masked.twilio.authToken ? '••••••••' : '', apiSecret: masked.twilio.apiSecret ? '••••••••' : '', }; } // Mask OpenAI credentials if (masked.openai) { masked.openai = { ...masked.openai, apiKey: masked.openai.apiKey ? '••••••••' : '', }; } return masked; } }