From cefe3ef6c33374d5980a5722af78bbdce081aed9 Mon Sep 17 00:00:00 2001 From: Pedro Ivo Date: Fri, 15 Nov 2024 17:38:26 -0300 Subject: [PATCH 1/5] fix: chats must be unique --- prisma/mysql-schema.prisma | 1 + prisma/postgresql-schema.prisma | 1 + .../channel/whatsapp/whatsapp.baileys.service.ts | 10 ++++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index ff968882..55ab4d70 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -127,6 +127,7 @@ model Chat { unreadMessages Int @default(0) @@index([instanceId]) @@index([remoteJid]) + @@unique([remoteJid, instanceId]) } model Contact { diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index 011de9a0..eaff59e5 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -127,6 +127,7 @@ model Chat { unreadMessages Int @default(0) @@index([instanceId]) @@index([remoteJid]) + @@unique([remoteJid, instanceId]) } model Contact { diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 0afd5318..6261c17d 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1147,7 +1147,10 @@ export class BaileysStartupService extends ChannelStartupService { this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { - await this.prismaRepository.chat.create({ + await this.prismaRepository.chat.update({ + where: { + id: existingChat.id, + }, data: chatToInsert, }); } @@ -1483,7 +1486,10 @@ export class BaileysStartupService extends ChannelStartupService { this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { - await this.prismaRepository.chat.create({ + await this.prismaRepository.chat.update({ + where: { + id: existingChat.id, + }, data: chatToInsert, }); } From ecbbc5b090fd2d4342831430ab8b681b138f4399 Mon Sep 17 00:00:00 2001 From: Pedro Ivo Date: Sun, 17 Nov 2024 20:04:27 -0300 Subject: [PATCH 2/5] fix: avoid concurrency cases in label handler --- .../whatsapp/whatsapp.baileys.service.ts | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 6261c17d..3223cb01 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1529,6 +1529,8 @@ export class BaileysStartupService extends ChannelStartupService { private readonly labelHandle = { [Events.LABELS_EDIT]: async (label: Label) => { + this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name }); + const labelsRepository = await this.prismaRepository.label.findMany({ where: { instanceId: this.instanceId }, }); @@ -1563,7 +1565,6 @@ export class BaileysStartupService extends ChannelStartupService { create: labelData, }); } - this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name }); } }, @@ -1571,26 +1572,44 @@ export class BaileysStartupService extends ChannelStartupService { data: { association: LabelAssociation; type: 'remove' | 'add' }, database: Database, ) => { + this.logger.info( + `labels as sociation - ${data?.association?.chatId} (${data.type}): ${data?.association?.labelId}`, + ); if (database.SAVE_DATA.CHATS) { - const chats = await this.prismaRepository.chat.findMany({ - where: { instanceId: this.instanceId }, - }); - const chat = chats.find((c) => c.remoteJid === data.association.chatId); - if (chat) { - const labelsArray = Array.isArray(chat.labels) ? chat.labels.map((event) => String(event)) : []; - let labels = [...labelsArray]; - - if (data.type === 'remove') { - labels = labels.filter((label) => label !== data.association.labelId); - } else if (data.type === 'add') { - labels = [...labels, data.association.labelId]; - } - await this.prismaRepository.chat.update({ - where: { id: chat.id }, - data: { - labels, - }, - }); + const instanceId = this.instanceId; + const chatId = data.association.chatId; + const labelId = data.association.labelId; + + if (data.type === 'add') { + // Adicionar o label ao array JSONB + await this.prismaRepository.$executeRawUnsafe( + `UPDATE "Chat" + SET "labels" = (SELECT to_jsonb(array_agg(DISTINCT elem)) + FROM (SELECT jsonb_array_elements_text("labels") AS elem + UNION + SELECT $1::text AS elem) sub) + WHERE "instanceId" = $2 + AND "remoteJid" = $3`, + labelId, + instanceId, + chatId, + ); + } else if (data.type === 'remove') { + // Usar consulta SQL bruta para remover o label + await this.prismaRepository.$executeRawUnsafe( + `UPDATE "Chat" + SET "labels" = COALESCE( + (SELECT jsonb_agg(elem) + FROM jsonb_array_elements_text("labels") AS elem + WHERE elem <> $1), + '[]' ::jsonb + ) + WHERE "instanceId" = $2 + AND "remoteJid" = $3;`, + labelId, + instanceId, + chatId, + ); } } @@ -4229,6 +4248,7 @@ export class BaileysStartupService extends ChannelStartupService { throw new BadRequestException('Unable to leave the group', error.toString()); } } + public async templateMessage() { throw new Error('Method not available in the Baileys service'); } From a42bc988ecfa76f2309624af19a86e6ed7205306 Mon Sep 17 00:00:00 2001 From: Pedro Ivo Date: Tue, 19 Nov 2024 10:15:08 -0300 Subject: [PATCH 3/5] fix: add/remove saving on db and improve add query for startup case --- package.json | 1 + .../whatsapp/whatsapp.baileys.service.ts | 85 ++++++++++++------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index f7e77194..6322d5cd 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", + "cuid": "^3.0.0", "dayjs": "^1.11.7", "dotenv": "^16.4.5", "eventemitter2": "^6.4.9", diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 3223cb01..41cfb391 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -125,6 +125,7 @@ import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; import { spawn } from 'child_process'; import { isArray, isBase64, isURL } from 'class-validator'; import { randomBytes } from 'crypto'; +import cuid from 'cuid'; import EventEmitter2 from 'eventemitter2'; import ffmpeg from 'fluent-ffmpeg'; import FormData from 'form-data'; @@ -1573,7 +1574,7 @@ export class BaileysStartupService extends ChannelStartupService { database: Database, ) => { this.logger.info( - `labels as sociation - ${data?.association?.chatId} (${data.type}): ${data?.association?.labelId}`, + `labels association - ${data?.association?.chatId} (${data.type}-${data?.association?.type}): ${data?.association?.labelId}`, ); if (database.SAVE_DATA.CHATS) { const instanceId = this.instanceId; @@ -1581,35 +1582,9 @@ export class BaileysStartupService extends ChannelStartupService { const labelId = data.association.labelId; if (data.type === 'add') { - // Adicionar o label ao array JSONB - await this.prismaRepository.$executeRawUnsafe( - `UPDATE "Chat" - SET "labels" = (SELECT to_jsonb(array_agg(DISTINCT elem)) - FROM (SELECT jsonb_array_elements_text("labels") AS elem - UNION - SELECT $1::text AS elem) sub) - WHERE "instanceId" = $2 - AND "remoteJid" = $3`, - labelId, - instanceId, - chatId, - ); + await this.addLabel(labelId, instanceId, chatId); } else if (data.type === 'remove') { - // Usar consulta SQL bruta para remover o label - await this.prismaRepository.$executeRawUnsafe( - `UPDATE "Chat" - SET "labels" = COALESCE( - (SELECT jsonb_agg(elem) - FROM jsonb_array_elements_text("labels") AS elem - WHERE elem <> $1), - '[]' ::jsonb - ) - WHERE "instanceId" = $2 - AND "remoteJid" = $3;`, - labelId, - instanceId, - chatId, - ); + await this.removeLabel(labelId, instanceId, chatId); } } @@ -3886,7 +3861,7 @@ export class BaileysStartupService extends ChannelStartupService { })); } - public async handleLabel(data: HandleLabelDto) { + public async handleLabel(data: HandleLabelDto, instanceId: string) { const whatsappContact = await this.whatsappNumber({ numbers: [data.number] }); if (whatsappContact.length === 0) { throw new NotFoundException('Number not found'); @@ -3899,11 +3874,13 @@ export class BaileysStartupService extends ChannelStartupService { try { if (data.action === 'add') { await this.client.addChatLabel(contact.jid, data.labelId); + await this.addLabel(data.labelId, instanceId, contact.jid); return { numberJid: contact.jid, labelId: data.labelId, add: true }; } if (data.action === 'remove') { await this.client.removeChatLabel(contact.jid, data.labelId); + await this.removeLabel(data.labelId, instanceId, contact.jid); return { numberJid: contact.jid, labelId: data.labelId, remove: true }; } @@ -4352,4 +4329,52 @@ export class BaileysStartupService extends ChannelStartupService { return unreadMessages; } + + private async addLabel(labelId: string, instanceId: string, chatId: string) { + const id = cuid(); + + await this.prismaRepository.$executeRawUnsafe( + `INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt") + VALUES ($4, $2, $3, to_jsonb(ARRAY[$1]::text[]), NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid") + DO + UPDATE + SET "labels" = ( + SELECT to_jsonb(array_agg(DISTINCT elem)) + FROM ( + SELECT jsonb_array_elements_text("Chat"."labels") AS elem + UNION + SELECT $1::text AS elem + ) sub + ), + "updatedAt" = NOW();`, + labelId, + instanceId, + chatId, + id, + ); + } + + private async removeLabel(labelId: string, instanceId: string, chatId: string) { + const id = cuid(); + + await this.prismaRepository.$executeRawUnsafe( + `INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt") + VALUES ($4, $2, $3, '[]'::jsonb, NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid") + DO + UPDATE + SET "labels" = COALESCE ( + ( + SELECT jsonb_agg(elem) + FROM jsonb_array_elements_text("Chat"."labels") AS elem + WHERE elem <> $1 + ), + '[]'::jsonb + ), + "updatedAt" = NOW();`, + labelId, + instanceId, + chatId, + id, + ); + } } From 013fa9dc08a5cb250cb0e8700a14a85fba4282e1 Mon Sep 17 00:00:00 2001 From: Pedro Ivo Date: Tue, 19 Nov 2024 10:17:49 -0300 Subject: [PATCH 4/5] fix: join is considering instance id --- src/api/services/channel.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 146375cb..84d40676 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -655,8 +655,8 @@ export class ChannelStartupService { (ARRAY_AGG("Message"."sessionId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_session_id, (ARRAY_AGG("Message"."status" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_status FROM "Chat" - LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid" - LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" + LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid" AND "Chat"."instanceId" = "Message"."instanceId" + LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" AND "Chat"."instanceId" = "Contact"."instanceId" WHERE "Chat"."instanceId" = ${this.instanceId} GROUP BY @@ -690,8 +690,8 @@ export class ChannelStartupService { (ARRAY_AGG("Message"."sessionId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_session_id, (ARRAY_AGG("Message"."status" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_status FROM "Chat" - LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid" - LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" + LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid" AND "Chat"."instanceId" = "Message"."instanceId" + LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" AND "Chat"."instanceId" = "Contact"."instanceId" WHERE "Chat"."instanceId" = ${this.instanceId} AND "Chat"."remoteJid" = ${remoteJid} and "Message"."messageType" != 'reactionMessage' GROUP BY From 6e6711a5af488a9d5486511b964cdfd9bb0c3f5c Mon Sep 17 00:00:00 2001 From: Pedro Ivo Date: Tue, 19 Nov 2024 10:26:01 -0300 Subject: [PATCH 5/5] fix: get instance id in right place on handle label --- .../channel/whatsapp/whatsapp.baileys.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 41cfb391..a4824a97 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3861,7 +3861,7 @@ export class BaileysStartupService extends ChannelStartupService { })); } - public async handleLabel(data: HandleLabelDto, instanceId: string) { + public async handleLabel(data: HandleLabelDto) { const whatsappContact = await this.whatsappNumber({ numbers: [data.number] }); if (whatsappContact.length === 0) { throw new NotFoundException('Number not found'); @@ -3874,13 +3874,13 @@ export class BaileysStartupService extends ChannelStartupService { try { if (data.action === 'add') { await this.client.addChatLabel(contact.jid, data.labelId); - await this.addLabel(data.labelId, instanceId, contact.jid); + await this.addLabel(data.labelId, this.instanceId, contact.jid); return { numberJid: contact.jid, labelId: data.labelId, add: true }; } if (data.action === 'remove') { await this.client.removeChatLabel(contact.jid, data.labelId); - await this.removeLabel(data.labelId, instanceId, contact.jid); + await this.removeLabel(data.labelId, this.instanceId, contact.jid); return { numberJid: contact.jid, labelId: data.labelId, remove: true }; }