From 4ee4ee3a8bb578c65a9037100d387b05845baddf Mon Sep 17 00:00:00 2001 From: ricael Date: Fri, 15 Aug 2025 15:42:40 -0300 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20implementa=20resolu=C3=A7=C3=A3o=20a?= =?UTF-8?q?utom=C3=A1tica=20de=20contatos=20@lid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../whatsapp/whatsapp.baileys.service.ts | 318 +++++++++++++++--- 1 file changed, 280 insertions(+), 38 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 758a5bf96..22e7e2a88 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -670,6 +670,8 @@ export class BaileysStartupService extends ChannelStartupService { this.eventHandler(); + this.startLidCleanupScheduler(); + this.client.ws.on('CB:call', (packet) => { console.log('CB:call', packet); const payload = { event: 'CB:call', packet: packet }; @@ -1049,8 +1051,12 @@ export class BaileysStartupService extends ChannelStartupService { try { for (const received of messages) { if (received.key.remoteJid?.includes('@lid') && received.key.senderPn) { + this.logger.verbose(`Processing @lid message: ${received.key.remoteJid} -> ${received.key.senderPn}`); + (received.key as { previousRemoteJid?: string | null }).previousRemoteJid = received.key.remoteJid; received.key.remoteJid = received.key.senderPn; + + await this.updateContactFromLid(received.key.previousRemoteJid, received.key.remoteJid); } if ( received?.messageStubParameters?.some?.((param) => @@ -1446,16 +1452,7 @@ export class BaileysStartupService extends ChannelStartupService { } } - const findMessage = await this.prismaRepository.message.findFirst({ - where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, - }); - - if (!findMessage) { - continue; - } - const message: any = { - messageId: findMessage.id, keyId: key.id, remoteJid: key?.remoteJid, fromMe: key.fromMe, @@ -1465,6 +1462,16 @@ export class BaileysStartupService extends ChannelStartupService { instanceId: this.instanceId, }; + let findMessage: any; + const configDatabaseData = this.configService.get('DATABASE').SAVE_DATA; + if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) { + findMessage = await this.prismaRepository.message.findFirst({ + where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, + }); + + if (findMessage) message.messageId = findMessage.id; + } + if (update.message === null && update.status === undefined) { this.sendDataWebhook(Events.MESSAGES_DELETE, key); @@ -1480,7 +1487,9 @@ export class BaileysStartupService extends ChannelStartupService { } continue; - } else if (update.status !== undefined && status[update.status] !== findMessage.status) { + } + + if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { if (!key.fromMe && key.remoteJid) { readChatToUpdate[key.remoteJid] = true; @@ -3438,17 +3447,20 @@ export class BaileysStartupService extends ChannelStartupService { where: { id: message.id }, data: { key: { ...existingKey, deleted: true }, status: 'DELETED' }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: response.key.remoteJid, - fromMe: response.key.fromMe, - participant: response.key?.remoteJid, - status: 'DELETED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: response.key.remoteJid, + fromMe: response.key.fromMe, + participant: response.key?.remoteJid, + status: 'DELETED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } else { + if (!message) return response; await this.prismaRepository.message.deleteMany({ where: { id: message.id } }); } this.sendDataWebhook(Events.MESSAGES_DELETE, { @@ -3780,6 +3792,10 @@ export class BaileysStartupService extends ChannelStartupService { private async formatUpdateMessage(data: UpdateMessageDto) { try { + if (!this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + return data; + } + const msg: any = await this.getMessage(data.key, true); if (msg?.messageType === 'conversation' || msg?.messageType === 'extendedTextMessage') { @@ -3813,13 +3829,15 @@ export class BaileysStartupService extends ChannelStartupService { try { const oldMessage: any = await this.getMessage(data.key, true); - if (!oldMessage) throw new NotFoundException('Message not found'); - if (oldMessage?.key?.remoteJid !== jid) { - throw new BadRequestException('RemoteJid does not match'); - } - if (oldMessage?.messageTimestamp > Date.now() + 900000) { - // 15 minutes in milliseconds - throw new BadRequestException('Message is older than 15 minutes'); + if (this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + if (!oldMessage) throw new NotFoundException('Message not found'); + if (oldMessage?.key?.remoteJid !== jid) { + throw new BadRequestException('RemoteJid does not match'); + } + if (oldMessage?.messageTimestamp > Date.now() + 900000) { + // 15 minutes in milliseconds + throw new BadRequestException('Message is older than 15 minutes'); + } } const messageSent = await this.client.sendMessage(jid, { ...(options as any), edit: data.key }); @@ -3837,7 +3855,7 @@ export class BaileysStartupService extends ChannelStartupService { ); const messageId = messageSent.message?.protocolMessage?.key?.id; - if (messageId) { + if (messageId && this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { let message = await this.prismaRepository.message.findFirst({ where: { key: { path: ['id'], equals: messageId } }, }); @@ -3849,6 +3867,7 @@ export class BaileysStartupService extends ChannelStartupService { if ((message.key.valueOf() as any)?.deleted) { new BadRequestException('You cannot edit deleted messages'); } + if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') { oldMessage.message.conversation = data.text; } else { @@ -3862,16 +3881,19 @@ export class BaileysStartupService extends ChannelStartupService { messageTimestamp: Math.floor(Date.now() / 1000), // Convert to int32 by dividing by 1000 to get seconds }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: messageSent.key.remoteJid, - fromMe: messageSent.key.fromMe, - participant: messageSent.key?.remoteJid, - status: 'EDITED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: messageSent.key.remoteJid, + fromMe: messageSent.key.fromMe, + participant: messageSent.key?.remoteJid, + status: 'EDITED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } } } @@ -3941,6 +3963,226 @@ export class BaileysStartupService extends ChannelStartupService { } } + /** + * Atualiza contatos que foram criados com @lid para o JID real + * Isso resolve problemas de mensagens não chegando no iPhone + * Funciona com ou sem banco de dados + */ + private async updateContactFromLid(lidJid: string, realJid: string) { + try { + // Verificar se o banco de dados está habilitado + const db = this.configService.get('DATABASE'); + const cache = this.configService.get('CACHE'); + + if (db.SAVE_DATA.CONTACTS) { + // Com banco de dados - usar Prisma + try { + // Buscar contato com @lid + const lidContact = await this.prismaRepository.contact.findFirst({ + where: { + remoteJid: lidJid, + instanceId: this.instanceId, + }, + }); + + if (lidContact) { + // Atualizar para o JID real + await this.prismaRepository.contact.update({ + where: { id: lidContact.id }, + data: { remoteJid: realJid }, + }); + + this.logger.verbose(`Updated contact from @lid: ${lidJid} -> ${realJid}`); + } + + // Também atualizar mensagens com @lid + const lidMessages = await this.prismaRepository.message.findMany({ + where: { + instanceId: this.instanceId, + key: { + path: ['remoteJid'], + equals: lidJid, + }, + }, + }); + + if (lidMessages.length > 0) { + for (const message of lidMessages) { + const key = message.key as any; + key.remoteJid = realJid; + + await this.prismaRepository.message.update({ + where: { id: message.id }, + data: { key: key }, + }); + } + + this.logger.verbose(`Updated ${lidMessages.length} messages from @lid: ${lidJid} -> ${realJid}`); + } + } catch (dbError) { + this.logger.warn(`Database operation failed, falling back to cache: ${dbError.message}`); + } + } + + // Sem banco de dados - usar cache e arquivos locais + if (cache?.REDIS?.ENABLED) { + // Atualizar no cache Redis + try { + const cacheKey = `contact:${this.instanceId}:${lidJid}`; + const realContactKey = `contact:${this.instanceId}:${realJid}`; + + // Buscar dados do contato @lid no cache + const lidContactData = await this.cache.hGet(this.instanceId, cacheKey); + if (lidContactData) { + // Atualizar para o JID real no cache + await this.cache.hSet(this.instanceId, realContactKey, lidContactData); + await this.cache.hDelete(this.instanceId, cacheKey); + + this.logger.verbose(`Updated Redis cache contact from @lid: ${lidJid} -> ${realJid}`); + } + } catch (cacheError) { + this.logger.warn(`Redis cache operation failed: ${cacheError.message}`); + } + } + + // Atualizar arquivos locais se necessário + if (this.instance.authState) { + try { + // Atualizar o estado de autenticação local + const authState = this.instance.authState as any; + if (authState.store && authState.store.contacts) { + // Atualizar contatos no store local + const contacts = authState.store.contacts; + if (contacts[lidJid]) { + contacts[realJid] = contacts[lidJid]; + delete contacts[lidJid]; + + this.logger.verbose(`Updated local auth state contact from @lid: ${lidJid} -> ${realJid}`); + } + } + } catch (localError) { + this.logger.warn(`Local auth state update failed: ${localError.message}`); + } + } + + this.logger.info(`Successfully processed @lid update: ${lidJid} -> ${realJid}`); + } catch (error) { + this.logger.error('Error updating contact from @lid:', error); + } + } + + /** + * Limpa contatos @lid órfãos e faz manutenção periódica + * Executa automaticamente para resolver problemas de mensagens não chegando + */ + private async cleanupOrphanedLidContacts() { + try { + this.logger.verbose('Starting cleanup of orphaned @lid contacts...'); + + const db = this.configService.get('DATABASE'); + const cache = this.configService.get('CACHE'); + + if (db.SAVE_DATA.CONTACTS) { + // Com banco: buscar todos os contatos @lid + try { + const lidContacts = await this.prismaRepository.contact.findMany({ + where: { + remoteJid: { contains: '@lid' }, + instanceId: this.instanceId, + }, + }); + + this.logger.verbose(`Found ${lidContacts.length} @lid contacts to cleanup`); + + for (const contact of lidContacts) { + // Tentar resolver o JID real através do WhatsApp + try { + // Usar o cliente WhatsApp para verificar se o contato existe + const contactInfo = await this.client.contactsUpsert([ + { id: contact.remoteJid, name: contact.pushName || 'Unknown' } + ]); + + if (contactInfo && contactInfo[0] && !contactInfo[0].id.includes('@lid')) { + // Contato foi resolvido, atualizar + await this.updateContactFromLid(contact.remoteJid, contactInfo[0].id); + } else { + // Contato não pode ser resolvido, remover + this.logger.warn(`Removing orphaned @lid contact: ${contact.remoteJid}`); + await this.prismaRepository.contact.delete({ + where: { id: contact.id } + }); + } + } catch (contactError) { + this.logger.warn(`Could not resolve contact ${contact.remoteJid}: ${contactError.message}`); + } + } + } catch (dbError) { + this.logger.warn(`Database cleanup failed: ${dbError.message}`); + } + } + + // Limpeza de cache Redis + if (cache?.REDIS?.ENABLED) { + try { + const keys = await this.cache.keys('*@lid*'); + this.logger.verbose(`Found ${keys.length} @lid keys in Redis cache`); + + for (const key of keys) { + // Tentar resolver e atualizar + const contactData = await this.cache.hGet(this.instanceId, key); + if (contactData) { + this.logger.verbose(`Processing Redis cache key: ${key}`); + for (const key of keys) { + const contactData = await this.cache.hGet(this.instanceId, key); + if (contactData) { + try { + // Extrai o JID @lid da chave do cache + const lidJid = key.split(':').pop(); + // Usa o Baileys para tentar resolver o JID real + const contactInfo = await this.client.contactsUpsert([ + { id: lidJid, name: contactData.pushName || 'Unknown' } + ]); + if (contactInfo && contactInfo[0] && !contactInfo[0].id.includes('@lid')) { + // Atualiza o cache para o JID real + const realContactKey = `contact:${this.instanceId}:${contactInfo[0].id}`; + await this.cache.hSet(this.instanceId, realContactKey, contactData); + await this.cache.hDelete(this.instanceId, key); + this.logger.verbose(`Updated Redis cache contact from @lid: ${key} -> ${realContactKey}`); + } + } catch (resolveError) { + this.logger.warn(`Could not resolve contact in cache: ${key} - ${resolveError.message}`); + } + } + } + } + } + } catch (cacheError) { + this.logger.warn(`Redis cleanup failed: ${cacheError.message}`); + } + } + + this.logger.info('Completed cleanup of orphaned @lid contacts'); + } catch (error) { + this.logger.error('Error during @lid cleanup:', error); + } + } + + /** + * Inicia o processo de limpeza periódica de @lid + * Executa a cada 5 minutos para manter o sistema limpo + */ + private startLidCleanupScheduler() { + // Limpeza inicial + setTimeout(() => this.cleanupOrphanedLidContacts(), 30000); // 30 segundos após inicialização + + // Limpeza periódica a cada 5 minutos + setInterval(() => { + this.cleanupOrphanedLidContacts(); + }, 5 * 60 * 1000); // 5 minutos + + this.logger.info('Started periodic @lid cleanup scheduler (every 5 minutes)'); + } + private getGroupMetadataCache = async (groupJid: string) => { if (!isJidGroup(groupJid)) return null; From ceb946c3dd07a3fa4578f1493e6941ca9bc0582b Mon Sep 17 00:00:00 2001 From: ricael Date: Fri, 15 Aug 2025 16:04:33 -0300 Subject: [PATCH 2/4] fix: build error --- .../whatsapp/whatsapp.baileys.service.ts | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 22e7e2a88..30223a3bf 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1052,11 +1052,9 @@ export class BaileysStartupService extends ChannelStartupService { for (const received of messages) { if (received.key.remoteJid?.includes('@lid') && received.key.senderPn) { this.logger.verbose(`Processing @lid message: ${received.key.remoteJid} -> ${received.key.senderPn}`); - - (received.key as { previousRemoteJid?: string | null }).previousRemoteJid = received.key.remoteJid; + const previousRemoteJid = received.key.remoteJid; received.key.remoteJid = received.key.senderPn; - - await this.updateContactFromLid(received.key.previousRemoteJid, received.key.remoteJid); + await this.updateContactFromLid(previousRemoteJid, received.key.remoteJid); } if ( received?.messageStubParameters?.some?.((param) => @@ -4067,7 +4065,7 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.info(`Successfully processed @lid update: ${lidJid} -> ${realJid}`); } catch (error) { - this.logger.error('Error updating contact from @lid:', error); + this.logger.error(`Error updating contact from @lid: ${lidJid}`); } } @@ -4098,13 +4096,10 @@ export class BaileysStartupService extends ChannelStartupService { // Tentar resolver o JID real através do WhatsApp try { // Usar o cliente WhatsApp para verificar se o contato existe - const contactInfo = await this.client.contactsUpsert([ - { id: contact.remoteJid, name: contact.pushName || 'Unknown' } - ]); - - if (contactInfo && contactInfo[0] && !contactInfo[0].id.includes('@lid')) { + const contactInfo = await this.client.onWhatsApp(contact.remoteJid); + if (contactInfo && contactInfo.length > 0 && contactInfo[0].jid && !contactInfo[0].jid.includes('@lid')) { // Contato foi resolvido, atualizar - await this.updateContactFromLid(contact.remoteJid, contactInfo[0].id); + await this.updateContactFromLid(contact.remoteJid, contactInfo[0].jid); } else { // Contato não pode ser resolvido, remover this.logger.warn(`Removing orphaned @lid contact: ${contact.remoteJid}`); @@ -4139,12 +4134,10 @@ export class BaileysStartupService extends ChannelStartupService { // Extrai o JID @lid da chave do cache const lidJid = key.split(':').pop(); // Usa o Baileys para tentar resolver o JID real - const contactInfo = await this.client.contactsUpsert([ - { id: lidJid, name: contactData.pushName || 'Unknown' } - ]); - if (contactInfo && contactInfo[0] && !contactInfo[0].id.includes('@lid')) { + const contactInfo = await this.client.onWhatsApp(lidJid); + if (contactInfo && contactInfo.length > 0 && contactInfo[0].jid && !contactInfo[0].jid.includes('@lid')) { // Atualiza o cache para o JID real - const realContactKey = `contact:${this.instanceId}:${contactInfo[0].id}`; + const realContactKey = `contact:${this.instanceId}:${contactInfo[0].jid}`; await this.cache.hSet(this.instanceId, realContactKey, contactData); await this.cache.hDelete(this.instanceId, key); this.logger.verbose(`Updated Redis cache contact from @lid: ${key} -> ${realContactKey}`); @@ -4163,7 +4156,7 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.info('Completed cleanup of orphaned @lid contacts'); } catch (error) { - this.logger.error('Error during @lid cleanup:', error); + this.logger.error(`Error during @lid cleanup`); } } From 5013e510da5510c549ca6d39dfa6be64798229b9 Mon Sep 17 00:00:00 2001 From: ricaelchiquetti Date: Fri, 15 Aug 2025 17:19:08 -0300 Subject: [PATCH 3/4] Update src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .../integrations/channel/whatsapp/whatsapp.baileys.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 30223a3bf..97810d3bf 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -4050,7 +4050,7 @@ export class BaileysStartupService extends ChannelStartupService { const authState = this.instance.authState as any; if (authState.store && authState.store.contacts) { // Atualizar contatos no store local - const contacts = authState.store.contacts; + const {contacts} = authState.store; if (contacts[lidJid]) { contacts[realJid] = contacts[lidJid]; delete contacts[lidJid]; From b7b3f1c01e12a992e747663ff061f6bf845176b6 Mon Sep 17 00:00:00 2001 From: ricael Date: Mon, 8 Sep 2025 08:38:10 -0300 Subject: [PATCH 4/4] feat: implement standardized error handling for WhatsApp API responses --- src/api/routes/business.router.ts | 49 +++++++++++++++++++--------- src/api/routes/template.router.ts | 49 +++++++++++++++++++--------- src/api/services/template.service.ts | 26 ++++++++++++--- src/utils/errorResponse.ts | 47 ++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 35 deletions(-) create mode 100644 src/utils/errorResponse.ts diff --git a/src/api/routes/business.router.ts b/src/api/routes/business.router.ts index 1e510a4ff..8a56bba4d 100644 --- a/src/api/routes/business.router.ts +++ b/src/api/routes/business.router.ts @@ -3,6 +3,7 @@ import { NumberDto } from '@api/dto/chat.dto'; import { businessController } from '@api/server.module'; import { catalogSchema, collectionsSchema } from '@validate/validate.schema'; import { RequestHandler, Router } from 'express'; +import { createMetaErrorResponse } from '@utils/errorResponse'; import { HttpStatus } from './index.router'; @@ -11,27 +12,45 @@ export class BusinessRouter extends RouterBroker { super(); this.router .post(this.routerPath('getCatalog'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: catalogSchema, - ClassRef: NumberDto, - execute: (instance, data) => businessController.fetchCatalog(instance, data), - }); + try { + const response = await this.dataValidate({ + request: req, + schema: catalogSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCatalog(instance, data), + }); - return res.status(HttpStatus.OK).json(response); + return res.status(HttpStatus.OK).json(response); + } catch (error) { + // Log error for debugging + console.error('Business catalog error:', error); + + // Use utility function to create standardized error response + const errorResponse = createMetaErrorResponse(error, 'business_catalog'); + return res.status(errorResponse.status).json(errorResponse); + } }) .post(this.routerPath('getCollections'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: collectionsSchema, - ClassRef: NumberDto, - execute: (instance, data) => businessController.fetchCollections(instance, data), - }); + try { + const response = await this.dataValidate({ + request: req, + schema: collectionsSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCollections(instance, data), + }); - return res.status(HttpStatus.OK).json(response); + return res.status(HttpStatus.OK).json(response); + } catch (error) { + // Log error for debugging + console.error('Business collections error:', error); + + // Use utility function to create standardized error response + const errorResponse = createMetaErrorResponse(error, 'business_collections'); + return res.status(errorResponse.status).json(errorResponse); + } }); } public readonly router: Router = Router(); -} +} \ No newline at end of file diff --git a/src/api/routes/template.router.ts b/src/api/routes/template.router.ts index b77b7d834..9a956e949 100644 --- a/src/api/routes/template.router.ts +++ b/src/api/routes/template.router.ts @@ -5,6 +5,7 @@ import { templateController } from '@api/server.module'; import { ConfigService } from '@config/env.config'; import { instanceSchema, templateSchema } from '@validate/validate.schema'; import { RequestHandler, Router } from 'express'; +import { createMetaErrorResponse } from '@utils/errorResponse'; import { HttpStatus } from './index.router'; @@ -16,26 +17,44 @@ export class TemplateRouter extends RouterBroker { super(); this.router .post(this.routerPath('create'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: templateSchema, - ClassRef: TemplateDto, - execute: (instance, data) => templateController.createTemplate(instance, data), - }); + try { + const response = await this.dataValidate({ + request: req, + schema: templateSchema, + ClassRef: TemplateDto, + execute: (instance, data) => templateController.createTemplate(instance, data), + }); - res.status(HttpStatus.CREATED).json(response); + res.status(HttpStatus.CREATED).json(response); + } catch (error) { + // Log error for debugging + console.error('Template creation error:', error); + + // Use utility function to create standardized error response + const errorResponse = createMetaErrorResponse(error, 'template_creation'); + res.status(errorResponse.status).json(errorResponse); + } }) .get(this.routerPath('find'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: instanceSchema, - ClassRef: InstanceDto, - execute: (instance) => templateController.findTemplate(instance), - }); + try { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => templateController.findTemplate(instance), + }); - res.status(HttpStatus.OK).json(response); + res.status(HttpStatus.OK).json(response); + } catch (error) { + // Log error for debugging + console.error('Template find error:', error); + + // Use utility function to create standardized error response + const errorResponse = createMetaErrorResponse(error, 'template_find'); + res.status(errorResponse.status).json(errorResponse); + } }); } public readonly router: Router = Router(); -} +} \ No newline at end of file diff --git a/src/api/services/template.service.ts b/src/api/services/template.service.ts index 949f71c78..8cbdc486b 100644 --- a/src/api/services/template.service.ts +++ b/src/api/services/template.service.ts @@ -60,6 +60,13 @@ export class TemplateService { const response = await this.requestTemplate(postData, 'POST'); if (!response || response.error) { + // If there's an error from WhatsApp API, throw it with the real error data + if (response && response.error) { + // Create an error object that includes the template field for Meta errors + const metaError = new Error(response.error.message || 'WhatsApp API Error'); + (metaError as any).template = response.error; + throw metaError; + } throw new Error('Error to create template'); } @@ -75,8 +82,9 @@ export class TemplateService { return template; } catch (error) { - this.logger.error(error); - throw new Error('Error to create template'); + this.logger.error('Error in create template: ' + error); + // Propagate the real error instead of "engolindo" it + throw error; } } @@ -86,6 +94,7 @@ export class TemplateService { const version = this.configService.get('WA_BUSINESS').VERSION; urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`; const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` }; + if (method === 'GET') { const result = await axios.get(urlServer, { headers }); return result.data; @@ -94,8 +103,15 @@ export class TemplateService { return result.data; } } catch (e) { - this.logger.error(e.response.data); - return e.response.data.error; + this.logger.error('WhatsApp API request error: ' + (e.response?.data || e.message)); + + // Return the complete error response from WhatsApp API + if (e.response?.data) { + return e.response.data; + } + + // If no response data, throw connection error + throw new Error(`Connection error: ${e.message}`); } } -} +} \ No newline at end of file diff --git a/src/utils/errorResponse.ts b/src/utils/errorResponse.ts new file mode 100644 index 000000000..66b61e40b --- /dev/null +++ b/src/utils/errorResponse.ts @@ -0,0 +1,47 @@ +import { HttpStatus } from '@api/routes/index.router'; + +export interface MetaErrorResponse { + status: number; + error: string; + message: string; + details: { + whatsapp_error: string; + whatsapp_code: string | number; + error_user_title: string; + error_user_msg: string; + error_type: string; + error_subcode: number | null; + fbtrace_id: string | null; + context: string; + type: string; + }; + timestamp: string; +} + +/** + * Creates standardized error response for Meta/WhatsApp API errors + */ +export function createMetaErrorResponse(error: any, context: string): MetaErrorResponse { + // Extract Meta/WhatsApp specific error fields + const metaError = error.template || error; + const errorUserTitle = metaError.error_user_title || metaError.message || 'Unknown error'; + const errorUserMsg = metaError.error_user_msg || metaError.message || 'Unknown error'; + + return { + status: HttpStatus.BAD_REQUEST, + error: 'Bad Request', + message: errorUserTitle, + details: { + whatsapp_error: errorUserMsg, + whatsapp_code: metaError.code || 'UNKNOWN_ERROR', + error_user_title: errorUserTitle, + error_user_msg: errorUserMsg, + error_type: metaError.type || 'UNKNOWN', + error_subcode: metaError.error_subcode || null, + fbtrace_id: metaError.fbtrace_id || null, + context, + type: 'whatsapp_api_error' + }, + timestamp: new Date().toISOString() + }; +} \ No newline at end of file