Marketplace móvil para negocios locales mexicanos. Estructura inicial: - apps/backend (NestJS API) - apps/frontend (React Web) - apps/mobile (Expo/React Native) - apps/mcp-server (Claude MCP Server) - apps/whatsapp-service (WhatsApp Business API) - database/ (PostgreSQL DDL) - docs/ (Documentación) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
276 lines
13 KiB
JavaScript
276 lines
13 KiB
JavaScript
"use strict";
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
};
|
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.TenantIntegrationsService = void 0;
|
|
const common_1 = require("@nestjs/common");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const typeorm_2 = require("typeorm");
|
|
const config_1 = require("@nestjs/config");
|
|
const tenant_integration_credential_entity_1 = require("../entities/tenant-integration-credential.entity");
|
|
const tenant_whatsapp_number_entity_1 = require("../entities/tenant-whatsapp-number.entity");
|
|
const tenant_entity_1 = require("../../auth/entities/tenant.entity");
|
|
let TenantIntegrationsService = class TenantIntegrationsService {
|
|
constructor(credentialRepo, whatsappNumberRepo, tenantRepo, configService) {
|
|
this.credentialRepo = credentialRepo;
|
|
this.whatsappNumberRepo = whatsappNumberRepo;
|
|
this.tenantRepo = tenantRepo;
|
|
this.configService = configService;
|
|
}
|
|
async getWhatsAppCredentials(tenantId) {
|
|
const tenantCredential = await this.credentialRepo.findOne({
|
|
where: {
|
|
tenantId,
|
|
integrationType: tenant_integration_credential_entity_1.IntegrationType.WHATSAPP,
|
|
provider: tenant_integration_credential_entity_1.IntegrationProvider.META,
|
|
isActive: true,
|
|
},
|
|
});
|
|
if (tenantCredential && tenantCredential.credentials) {
|
|
const creds = tenantCredential.credentials;
|
|
if (creds.accessToken && creds.phoneNumberId) {
|
|
return {
|
|
accessToken: creds.accessToken,
|
|
phoneNumberId: creds.phoneNumberId,
|
|
businessAccountId: creds.businessAccountId,
|
|
verifyToken: creds.verifyToken,
|
|
isFromPlatform: false,
|
|
tenantId,
|
|
};
|
|
}
|
|
}
|
|
return this.getPlatformWhatsAppCredentials();
|
|
}
|
|
getPlatformWhatsAppCredentials() {
|
|
const accessToken = this.configService.get('WHATSAPP_ACCESS_TOKEN');
|
|
const phoneNumberId = this.configService.get('WHATSAPP_PHONE_NUMBER_ID');
|
|
if (!accessToken || !phoneNumberId) {
|
|
throw new common_1.BadRequestException('WhatsApp platform credentials not configured');
|
|
}
|
|
return {
|
|
accessToken,
|
|
phoneNumberId,
|
|
businessAccountId: this.configService.get('WHATSAPP_BUSINESS_ACCOUNT_ID'),
|
|
verifyToken: this.configService.get('WHATSAPP_VERIFY_TOKEN'),
|
|
isFromPlatform: true,
|
|
};
|
|
}
|
|
async resolveTenantFromPhoneNumberId(phoneNumberId) {
|
|
const whatsappNumber = await this.whatsappNumberRepo.findOne({
|
|
where: { phoneNumberId, isActive: true },
|
|
});
|
|
if (whatsappNumber) {
|
|
return whatsappNumber.tenantId;
|
|
}
|
|
const platformPhoneNumberId = this.configService.get('WHATSAPP_PHONE_NUMBER_ID');
|
|
if (phoneNumberId === platformPhoneNumberId) {
|
|
return null;
|
|
}
|
|
const credential = await this.credentialRepo.findOne({
|
|
where: {
|
|
integrationType: tenant_integration_credential_entity_1.IntegrationType.WHATSAPP,
|
|
isActive: true,
|
|
},
|
|
});
|
|
if (credential) {
|
|
const creds = credential.credentials;
|
|
if (creds.phoneNumberId === phoneNumberId) {
|
|
return credential.tenantId;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
async getLLMConfig(tenantId) {
|
|
const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } });
|
|
const preferredProvider = tenant?.preferredLlmProvider || 'openai';
|
|
const tenantCredential = await this.credentialRepo.findOne({
|
|
where: {
|
|
tenantId,
|
|
integrationType: tenant_integration_credential_entity_1.IntegrationType.LLM,
|
|
isActive: true,
|
|
},
|
|
});
|
|
if (tenantCredential && tenantCredential.credentials) {
|
|
const creds = tenantCredential.credentials;
|
|
const config = tenantCredential.config;
|
|
if (creds.apiKey) {
|
|
return {
|
|
apiKey: creds.apiKey,
|
|
provider: tenantCredential.provider,
|
|
model: config.model || this.getDefaultModel(tenantCredential.provider),
|
|
maxTokens: config.maxTokens || 1000,
|
|
temperature: config.temperature || 0.7,
|
|
baseUrl: config.baseUrl || this.getDefaultBaseUrl(tenantCredential.provider),
|
|
systemPrompt: config.systemPrompt,
|
|
isFromPlatform: false,
|
|
tenantId,
|
|
};
|
|
}
|
|
}
|
|
return this.getPlatformLLMConfig();
|
|
}
|
|
getPlatformLLMConfig() {
|
|
const apiKey = this.configService.get('OPENAI_API_KEY') ||
|
|
this.configService.get('LLM_API_KEY');
|
|
const provider = this.configService.get('LLM_PROVIDER', 'openai');
|
|
if (!apiKey) {
|
|
throw new common_1.BadRequestException('LLM platform credentials not configured');
|
|
}
|
|
return {
|
|
apiKey,
|
|
provider,
|
|
model: this.configService.get('LLM_MODEL', 'gpt-4o-mini'),
|
|
maxTokens: parseInt(this.configService.get('LLM_MAX_TOKENS', '1000')),
|
|
temperature: parseFloat(this.configService.get('LLM_TEMPERATURE', '0.7')),
|
|
baseUrl: this.configService.get('LLM_BASE_URL', 'https://api.openai.com/v1'),
|
|
isFromPlatform: true,
|
|
};
|
|
}
|
|
getDefaultModel(provider) {
|
|
const defaults = {
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OPENAI]: 'gpt-4o-mini',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OPENROUTER]: 'anthropic/claude-3-haiku',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.ANTHROPIC]: 'claude-3-haiku-20240307',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OLLAMA]: 'llama2',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.AZURE_OPENAI]: 'gpt-4o-mini',
|
|
};
|
|
return defaults[provider] || 'gpt-4o-mini';
|
|
}
|
|
getDefaultBaseUrl(provider) {
|
|
const defaults = {
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OPENAI]: 'https://api.openai.com/v1',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OPENROUTER]: 'https://openrouter.ai/api/v1',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.ANTHROPIC]: 'https://api.anthropic.com/v1',
|
|
[tenant_integration_credential_entity_1.IntegrationProvider.OLLAMA]: 'http://localhost:11434/v1',
|
|
};
|
|
return defaults[provider] || 'https://api.openai.com/v1';
|
|
}
|
|
async upsertCredential(tenantId, integrationType, provider, credentials, config, userId) {
|
|
let credential = await this.credentialRepo.findOne({
|
|
where: { tenantId, integrationType, provider },
|
|
});
|
|
if (credential) {
|
|
credential.credentials = credentials;
|
|
credential.config = config || credential.config;
|
|
credential.updatedBy = userId;
|
|
credential.isVerified = false;
|
|
}
|
|
else {
|
|
credential = this.credentialRepo.create({
|
|
tenantId,
|
|
integrationType,
|
|
provider,
|
|
credentials,
|
|
config: config || {},
|
|
createdBy: userId,
|
|
updatedBy: userId,
|
|
});
|
|
}
|
|
return this.credentialRepo.save(credential);
|
|
}
|
|
async getCredentials(tenantId) {
|
|
return this.credentialRepo.find({
|
|
where: { tenantId },
|
|
order: { integrationType: 'ASC', provider: 'ASC' },
|
|
});
|
|
}
|
|
async getCredential(tenantId, integrationType, provider) {
|
|
return this.credentialRepo.findOne({
|
|
where: { tenantId, integrationType, provider },
|
|
});
|
|
}
|
|
async deleteCredential(tenantId, integrationType, provider) {
|
|
await this.credentialRepo.delete({ tenantId, integrationType, provider });
|
|
}
|
|
async toggleCredential(tenantId, integrationType, provider, isActive) {
|
|
const credential = await this.credentialRepo.findOne({
|
|
where: { tenantId, integrationType, provider },
|
|
});
|
|
if (!credential) {
|
|
throw new common_1.NotFoundException('Credential not found');
|
|
}
|
|
credential.isActive = isActive;
|
|
return this.credentialRepo.save(credential);
|
|
}
|
|
async getIntegrationStatus(tenantId) {
|
|
const credentials = await this.getCredentials(tenantId);
|
|
const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } });
|
|
const whatsappCred = credentials.find((c) => c.integrationType === tenant_integration_credential_entity_1.IntegrationType.WHATSAPP && c.isActive);
|
|
const llmCred = credentials.find((c) => c.integrationType === tenant_integration_credential_entity_1.IntegrationType.LLM && c.isActive);
|
|
const stripeCred = credentials.find((c) => c.integrationType === tenant_integration_credential_entity_1.IntegrationType.STRIPE && c.isActive);
|
|
const mercadopagoCred = credentials.find((c) => c.integrationType === tenant_integration_credential_entity_1.IntegrationType.MERCADOPAGO && c.isActive);
|
|
const clipCred = credentials.find((c) => c.integrationType === tenant_integration_credential_entity_1.IntegrationType.CLIP && c.isActive);
|
|
return {
|
|
whatsapp: {
|
|
configured: !!whatsappCred,
|
|
usesPlatformNumber: tenant?.usesPlatformNumber ?? true,
|
|
provider: whatsappCred?.provider || 'meta',
|
|
isVerified: whatsappCred?.isVerified ?? false,
|
|
},
|
|
llm: {
|
|
configured: !!llmCred,
|
|
usesPlatformDefault: !llmCred,
|
|
provider: llmCred?.provider || tenant?.preferredLlmProvider || 'openai',
|
|
model: llmCred?.config?.model || 'gpt-4o-mini',
|
|
isVerified: llmCred?.isVerified ?? false,
|
|
},
|
|
payments: {
|
|
stripe: {
|
|
configured: !!stripeCred,
|
|
isVerified: stripeCred?.isVerified ?? false,
|
|
},
|
|
mercadopago: {
|
|
configured: !!mercadopagoCred,
|
|
isVerified: mercadopagoCred?.isVerified ?? false,
|
|
},
|
|
clip: {
|
|
configured: !!clipCred,
|
|
isVerified: clipCred?.isVerified ?? false,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
async registerWhatsAppNumber(tenantId, phoneNumberId, phoneNumber, displayName, isPlatformNumber = false) {
|
|
let mapping = await this.whatsappNumberRepo.findOne({
|
|
where: { phoneNumberId },
|
|
});
|
|
if (mapping) {
|
|
mapping.tenantId = tenantId;
|
|
mapping.phoneNumber = phoneNumber || mapping.phoneNumber;
|
|
mapping.displayName = displayName || mapping.displayName;
|
|
mapping.isPlatformNumber = isPlatformNumber;
|
|
}
|
|
else {
|
|
mapping = this.whatsappNumberRepo.create({
|
|
tenantId,
|
|
phoneNumberId,
|
|
phoneNumber,
|
|
displayName,
|
|
isPlatformNumber,
|
|
});
|
|
}
|
|
return this.whatsappNumberRepo.save(mapping);
|
|
}
|
|
};
|
|
exports.TenantIntegrationsService = TenantIntegrationsService;
|
|
exports.TenantIntegrationsService = TenantIntegrationsService = __decorate([
|
|
(0, common_1.Injectable)(),
|
|
__param(0, (0, typeorm_1.InjectRepository)(tenant_integration_credential_entity_1.TenantIntegrationCredential)),
|
|
__param(1, (0, typeorm_1.InjectRepository)(tenant_whatsapp_number_entity_1.TenantWhatsAppNumber)),
|
|
__param(2, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)),
|
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
config_1.ConfigService])
|
|
], TenantIntegrationsService);
|
|
//# sourceMappingURL=tenant-integrations.service.js.map
|