import { Injectable, Logger } from '@nestjs/common'; import { GenericMessageDto, IntegrationService } from './integration.service'; import { IcsMeetingService } from 'src/module/ics/service/ics.service'; import { LoggingService } from 'src/utils/service/loggingUtil.service'; import { ReflectionHelper } from '../../../utils/service/reflection-helper.service'; import { MediaDataService } from 'src/module/meta/service/media-data.service'; import axios from 'axios'; @Injectable() export class WrapperService { private readonly logger = new Logger(WrapperService.name); constructor( private readonly integrationService: IntegrationService, private readonly icsService: IcsMeetingService, private readonly loggingService: LoggingService, private readonly reflectionHelper: ReflectionHelper, private readonly mediaService: MediaDataService, ) {} /** * Wrapper for sending mail */ async sendMailWrapper(payload: any, loggedInUser: any) { try { this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `sendMailWrapper called. User: ${JSON.stringify( loggedInUser, )}, Payload: ${JSON.stringify(payload)}`, [], [], ); const { level_id, level_type, organization_id } = loggedInUser; // 1. Check for config at user’s level this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Fetching configs for level_id=${level_id}, level_type=${level_type}`, [], [], ); const intergrationConfigRepo = this.reflectionHelper.getRepoService('IntegrationConfig'); const configs = await intergrationConfigRepo.find({ where: { level_id: level_id, level_type: level_type, status: 1, integration_type: 'EMAIL', }, }); this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Configs found: ${JSON.stringify(configs)}`, [], [], ); let templateCode: string | undefined; if (payload.templateCode) { this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Looking up templateCode=${payload.templateCode} for current level`, [], [], ); const templateRepo = this.reflectionHelper.getRepoService('CommTemplate'); const template = await templateRepo.findOne({ where: { code: payload.templateCode, level_id: level_id, level_type: level_type, }, }); this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Template lookup result: ${JSON.stringify(template)}`, [], [], ); if (template) { templateCode = template.rich_text; } else { this.loggingService.log( 'warn', 'wrapperService', 'sendMailWrapper', `No template found for templateCode=${payload.templateCode} and current level`, [], [], ); } } let payloadSendMail: GenericMessageDto; if (configs && configs.length > 0) { this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Using user-level configs`, [], [], ); payloadSendMail = { levelId: level_id, levelType: level_type, app_code: 'DEFAULT', // Legacy wrapper default to: payload.to, message: payload.message, subject: payload.subject, type: 'EMAIL', cc: payload.cc, bcc: payload.bcc, html: payload.html, // attachments: payload.attachments, templateId: payload.templateId, variables: payload.variables, organization_id: loggedInUser.organization_id, }; } else { this.loggingService.log( 'warn', 'wrapperService', 'sendMailWrapper', `No user-level configs found. Falling back to ORG-level`, [], [], ); const fallbackConfigs = await intergrationConfigRepo.find({ where: { level_type: 'ORG', integration_type: 'EMAIL', status: 1, }, }); this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `ORG-level configs: ${JSON.stringify(fallbackConfigs)}`, [], [], ); if (!fallbackConfigs || fallbackConfigs.length === 0) { this.loggingService.log( 'warn', 'wrapperService', 'sendMailWrapper', 'No active email communication config found', [], [], ); } payloadSendMail = { levelId: 1, levelType: 'ORG', app_code: 'DEFAULT', // Legacy wrapper default to: payload.to, message: payload.message, subject: payload.subject, type: 'EMAIL', cc: payload.cc, bcc: payload.bcc, html: payload.html, // attachments: payload.attachments, // templateId: payload.templateId, variables: payload.variables, organization_id: loggedInUser.organization_id, }; } this.loggingService.log( 'debug', 'wrapperService', 'sendMailWrapper', `Final payload for IntegrationService: ${JSON.stringify( payloadSendMail, )}`, [], [], ); const result = await this.integrationService.sendGenericMessage(payloadSendMail); this.loggingService.log( 'log', 'wrapperService', 'sendMailWrapper', `sendMailWrapper SUCCESS. Result: ${JSON.stringify(result)}`, [], [], ); return { success: true, data: result, }; } catch (error: any) { this.loggingService.log( 'error', 'wrapperService', 'sendMailWrapper', `sendMailWrapper ERROR: ${error.message}`, [], [error.stack], // Sending stack trace as additional data ); return { success: false, error: error.message, }; } } /** * Wrapper for sending communication */ async sendCommunicationWrapperService(entity: any, loggedInUser: any) { try { this.loggingService.log( 'log', 'wrapperService', 'sendCommunicationWrapperService', `sendCommunicationWrapper called. User: ${JSON.stringify( loggedInUser, )}, Payload: ${JSON.stringify(entity)}`, [], [], ); const { level_id, level_type, appcode, organization_id } = loggedInUser; // Check if active configs exist for current user's level this.loggingService.log( 'debug', 'wrapperService', 'sendCommunicationWrapperService', `Checking active ${ entity.type || 'EMAIL' } configs for level_id=${level_id}, level_type=${level_type}, app_code=${appcode}`, [], [], ); let configs = await this.integrationService.getActiveConfigs( level_id, level_type, appcode || 'CRM', entity.type || 'EMAIL', ); let effectiveLevelId = level_id; let effectiveLevelType = level_type; // Fallback to ORG-level if no configs found if (!configs || configs.length === 0) { this.loggingService.log( 'warn', 'wrapperService', 'sendCommunicationWrapperService', `No active configs found for ${level_type}:${level_id}, falling back to ORG-level`, [], [], ); configs = await this.integrationService.getActiveConfigs( 1, 'ORG', appcode || 'DEFAULT', entity.type || 'EMAIL', ); effectiveLevelId = 1; effectiveLevelType = 'ORG'; } if (!configs || configs.length === 0) { this.loggingService.log( 'error', 'wrapperService', 'sendCommunicationWrapperService', 'No active configurations found at any level.', [], [], ); throw new Error('No active configurations found.'); } // Template lookup if templateCode is provided let resolvedTemplateId = entity?.templateId; if (entity.template_code) { this.loggingService.log( 'debug', 'wrapperService', 'sendCommunicationWrapperService', `Looking up template for code=${entity.template_code}, level_id=${level_id}, level_type=${level_type}`, [], [], ); // Try to find template at user's level first const commTemplateRepo = this.reflectionHelper.getRepoService('CommTemplate'); let templates = await commTemplateRepo.find({ where: { code: entity.template_code, level_id: level_id, level_type: level_type, }, }); // Fallback to ORG + organization_id if not found if (!templates || templates.length === 0) { this.loggingService.log( 'warn', 'wrapperService', 'sendCommunicationWrapperService', `No template found for ${entity.template_code} at provided level. Falling back to ORG-level.`, [], [], ); templates = await commTemplateRepo.find({ where: { code: entity.template_code, organization_id: organization_id, }, }); } if (templates && templates.length > 0) { resolvedTemplateId = templates[0]?.id; this.loggingService.log( 'debug', 'wrapperService', 'sendCommunicationWrapperService', `Template resolved for code=${entity.template_code}, id=${resolvedTemplateId}`, [], [], ); } else { this.loggingService.log( 'warn', 'wrapperService', 'sendCommunicationWrapperService', `No template found for code=${entity.template_code} at any level.`, [], [], ); } } // Step Prepare payload for Integration Service const mailPayload = { to: entity.to, subject: entity.subject, html: entity.message, cc: entity?.cc, bcc: entity?.bcc, templateId: resolvedTemplateId, // updated with resolved ID message: entity.message, type: entity.type || 'EMAIL', levelId: effectiveLevelId, levelType: effectiveLevelType, app_code: appcode, user_id: loggedInUser.id, entity_type: entity.entity_type, entity_id: entity.entity_id, attachments: entity.attachments, mediaUrl: entity.mediaUrl, organization_id: organization_id, enterprise_id: loggedInUser.enterprise_id, } as any; this.loggingService.log( 'debug', 'wrapperService', 'sendCommunicationWrapperService', `Final payload for sendGenericMessage: ${JSON.stringify(mailPayload)}`, [], [], ); // attachments: [1, 2, 3, 4] if (entity.attachments && entity.attachments.length > 0) { mailPayload.attachments = []; for (const attachmentId of entity.attachments) { // Step 1: Download or get media file const url = await this.mediaService.getMediaDownloadUrl( attachmentId, {}, 60000, ); if (!url?.signedUrl) continue; // Step 2: Fetch file content const response = await axios.get(url.signedUrl, { responseType: 'arraybuffer', }); // Step 3: Convert to base64 const base64Content = Buffer.from(response.data).toString('base64'); // Step 4: Push formatted attachment mailPayload.attachments.push({ content: base64Content, type: response.headers['content-type'] || 'application/octet-stream', filename: url.fileName, disposition: 'attachment', }); } } // Step 5️⃣ Send via Integration Service const result = await this.integrationService.sendGenericMessage(mailPayload); this.loggingService.log( 'log', 'wrapperService', 'sendCommunicationWrapperService', `sendCommunicationWrapper SUCCESS. Result: ${JSON.stringify(result)}`, [], [], ); return result; } catch (error: any) { this.loggingService.log( 'error', 'wrapperService', 'sendCommunicationWrapperService', `sendCommunicationWrapper ERROR: ${error.message}`, [], [error.stack], ); return { success: false, error: error.message, }; } } /** * Wrapper for scheduling meeting */ async scheduleMeetingWrapper( payload: any, loggedInUser: any, mapped_entities?: any, ) { try { this.loggingService.log( 'log', 'wrapperService', 'scheduleMeetingWrapper', `scheduleMeetingWrapper called by user=${ loggedInUser?.id || 'unknown' } Payload=${JSON.stringify(payload)}`, [], [], ); // Normalize emails (to, cc, bcc → attendees) const cleanEmails = (arr: string[] = []) => arr.filter((e) => e && e.trim() !== '').map((e) => e.trim()); const toList = Array.isArray(payload.to) ? cleanEmails(payload.to) : []; payload.attendees = [ ...toList.map((e) => ({ email: e })), ...(payload.attendees || []).flatMap((a) => typeof a === 'string' ? [{ email: a }] : Array.isArray(a?.email) ? a.email.map((e) => ({ email: e })) : a?.email ? [{ email: a.email }] : [], ), ]; let integrationConfig = await this.integrationService.getSingleActiveConfig( payload.level_id, payload.level_type, payload.app_code, 'EMAIL', ); if (integrationConfig) { return { success: true, data: await this.sendIcsInvite( payload, payload.level_id, payload.level_type, payload.app_code, loggedInUser.organization_id, mapped_entities, ), }; } // ---- Step 2: Check ORG-level configs ---- this.loggingService.log( 'log', 'wrapperService', 'scheduleMeetingWrapper', `No user-level config found, checking ORG-level...`, [], [], ); const integrationConfigRepo = this.reflectionHelper.getRepoService('IntegrationConfig'); const orgConfigs = await integrationConfigRepo.find({ where: { level_id: 1, level_type: 'ORG', integration_type: 'EMAIL', app_code: 'DEFAULT', status: 1, }, }); integrationConfig = await this.integrationService.getSingleActiveConfig( 1, 'ORG', 'DEFAULT', 'EMAIL', ); this.loggingService.log( 'debug', 'wrapperService', 'scheduleMeetingWrapper', `ORG configs found: ${JSON.stringify(orgConfigs)}`, [], [], ); if (integrationConfig) { return { success: true, data: await this.sendIcsInvite( payload, 1, 'ORG', 'DEFAULT', loggedInUser.organization_id, mapped_entities, ), }; } } catch (error: any) { this.loggingService.log( 'error', 'wrapperService', 'scheduleMeetingWrapper', `scheduleMeetingWrapper ERROR: ${error.message}`, [], [error.stack], ); return { success: false, error: error.message }; } } /** * Fetch credentials JSON by config ID */ private async getConfigCred(configId: number) { this.loggingService.log( 'debug', 'wrapperService', 'getConfigCred', `Fetching config JSON for configId=${configId}`, [], [], ); const integrationConfigRepo = this.reflectionHelper.getRepoService('IntegrationConfig'); const configRes = await integrationConfigRepo.findOne({ where: { id: configId, }, }); this.loggingService.log( 'debug', 'wrapperService', 'getConfigCred', `Config fetch result: ${JSON.stringify(configRes)}`, [], [], ); return configRes?.config_json || null; } private async sendIcsInvite( payload: any, levelId: number, levelType: string, appCode: string, organizationId: number, mapped_entities?: any, ) { this.loggingService.log( 'log', 'wrapperService', 'sendIcsInvite', `Generating ICS file. Payload: ${JSON.stringify(payload)}`, [], [], ); if (payload.template_code) { const commTemplateRepo = this.reflectionHelper.getRepoService('CommTemplate'); let templates = await commTemplateRepo.find({ where: { code: payload.template_code, level_id: levelId, level_type: levelType, }, }); if (templates && templates.length > 0) { payload['templateId'] = templates[0].id; } } let activeConfig = await this.integrationService.getSingleActiveConfig( levelId, levelType, appCode, 'EMAIL', ); const base64String = await this.icsService.generateIcs( payload, activeConfig?.config_json, ); this.loggingService.log( 'debug', 'wrapperService', 'sendIcsInvite', `ICS generated (base64 length: ${base64String?.length})`, [], [], ); // Normalize attendees const attendeeEmails = payload.attendees?.flatMap((a) => Array.isArray(a.email) ? a.email : [a.email], ) || []; // Combine into one list (to + attendees) const toList = Array.from( new Set( [ ...(Array.isArray(payload.to) ? payload.to : payload.to ? [payload.to] : []), ...attendeeEmails, ].map((email) => email.toLowerCase().trim()), ), ); const payloadSendMail: GenericMessageDto = { levelId, levelType, app_code: appCode, to: toList, message: payload.message, subject: payload.subject, type: 'EMAIL', cc: payload.cc, bcc: payload.bcc, html: payload.html, attachments: [ { content: base64String, type: 'text/calendar', filename: 'invite.ics', disposition: 'attachment', }, ], templateId: payload.templateId, entity_id: payload.entity_id, entity_type: payload.entity_type, organization_id: organizationId, mapped_entities, }; this.loggingService.log( 'debug', 'wrapperService', 'sendIcsInvite', `Final payload for ICS send: ${JSON.stringify(payloadSendMail)}`, [], [], ); const result = await this.integrationService.sendGenericMessage(payloadSendMail); this.loggingService.log( 'log', 'wrapperService', 'sendIcsInvite', `ICS mail send result: ${JSON.stringify(result)}`, [], [], ); return result; } }