171 lines
4.8 KiB
TypeScript
171 lines
4.8 KiB
TypeScript
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;
|
|
}
|
|
}
|