Merge pull request #2333 from augustolima1/fix/mysql-compat-lid

fix(mysql): compatibilidade da coluna lid e queries RAW
This commit is contained in:
Davidson Gomes
2026-02-24 12:06:02 -03:00
committed by GitHub
6 changed files with 356 additions and 151 deletions
@@ -131,8 +131,7 @@ ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRE
MODIFY `updatedAt` TIMESTAMP NOT NULL; MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable -- AlterTable
ALTER TABLE `IsOnWhatsapp` DROP COLUMN `lid`, ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL; MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable -- AlterTable
@@ -0,0 +1,21 @@
-- Re-add lid column that was incorrectly dropped by previous migration
-- This migration ensures backward compatibility for existing installations
-- Check if column exists before adding
SET @dbname = DATABASE();
SET @tablename = 'IsOnWhatsapp';
SET @columnname = 'lid';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` VARCHAR(100);')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
+1
View File
@@ -655,6 +655,7 @@ model IsOnWhatsapp {
id String @id @default(cuid()) id String @id @default(cuid())
remoteJid String @unique @db.VarChar(100) remoteJid String @unique @db.VarChar(100)
jidOptions String jidOptions String
lid String? @db.VarChar(100)
createdAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp createdAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp
} }
@@ -567,12 +567,27 @@ export class BaileysStartupService extends ChannelStartupService {
private async getMessage(key: proto.IMessageKey, full = false) { private async getMessage(key: proto.IMessageKey, full = false) {
try { try {
// Use raw SQL to avoid JSON path issues const provider = this.configService.get<Database>('DATABASE').PROVIDER;
const webMessageInfo = (await this.prismaRepository.$queryRaw`
SELECT * FROM "Message" let webMessageInfo: proto.IWebMessageInfo[];
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'id' = ${key.id} if (provider === 'mysql') {
`) as proto.IWebMessageInfo[]; // MySQL version
webMessageInfo = (await this.prismaRepository.$queryRaw`
SELECT * FROM Message
WHERE instanceId = ${this.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.id')) = ${key.id}
LIMIT 1
`) as proto.IWebMessageInfo[];
} else {
// PostgreSQL version
webMessageInfo = (await this.prismaRepository.$queryRaw`
SELECT * FROM "Message"
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'id' = ${key.id}
LIMIT 1
`) as proto.IWebMessageInfo[];
}
if (full) { if (full) {
return webMessageInfo[0]; return webMessageInfo[0];
@@ -1687,29 +1702,25 @@ export class BaileysStartupService extends ChannelStartupService {
} }
const searchId = originalMessageId || key.id; const searchId = originalMessageId || key.id;
const dbProvider = this.configService.get<Database>('DATABASE').PROVIDER;
let retries = 0; let messages: any[];
const maxRetries = 3; if (dbProvider === 'mysql') {
const retryDelay = 500; // 500ms delay to avoid blocking for too long messages = (await this.prismaRepository.$queryRaw`
SELECT * FROM Message
while (retries < maxRetries) { WHERE instanceId = ${this.instanceId}
const messages = (await this.prismaRepository.$queryRaw` AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.id')) = ${searchId}
LIMIT 1
`) as any[];
} else {
messages = (await this.prismaRepository.$queryRaw`
SELECT * FROM "Message" SELECT * FROM "Message"
WHERE "instanceId" = ${this.instanceId} WHERE "instanceId" = ${this.instanceId}
AND "key"->>'id' = ${searchId} AND "key"->>'id' = ${searchId}
LIMIT 1 LIMIT 1
`) as any[]; `) as any[];
findMessage = messages[0] || null;
if (findMessage?.id) {
break;
}
retries++;
if (retries < maxRetries) {
await delay(retryDelay);
}
} }
findMessage = messages[0] || null;
if (!findMessage?.id) { if (!findMessage?.id) {
this.logger.verbose( this.logger.verbose(
@@ -4835,16 +4846,32 @@ export class BaileysStartupService extends ChannelStartupService {
private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise<number> { private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise<number> {
if (timestamp === undefined || timestamp === null) return 0; if (timestamp === undefined || timestamp === null) return 0;
// Use raw SQL to avoid JSON path issues const provider = this.configService.get<Database>('DATABASE').PROVIDER;
const result = await this.prismaRepository.$executeRaw` let result: number;
UPDATE "Message"
SET "status" = ${status[4]} if (provider === 'mysql') {
WHERE "instanceId" = ${this.instanceId} // MySQL version
AND "key"->>'remoteJid' = ${remoteJid} result = await this.prismaRepository.$executeRaw`
AND ("key"->>'fromMe')::boolean = false UPDATE Message
AND "messageTimestamp" <= ${timestamp} SET status = ${status[4]}
AND ("status" IS NULL OR "status" = ${status[3]}) WHERE instanceId = ${this.instanceId}
`; AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.remoteJid')) = ${remoteJid}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.fromMe')) = 'false'
AND messageTimestamp <= ${timestamp}
AND (status IS NULL OR status = ${status[3]})
`;
} else {
// PostgreSQL version
result = await this.prismaRepository.$executeRaw`
UPDATE "Message"
SET "status" = ${status[4]}
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'remoteJid' = ${remoteJid}
AND ("key"->>'fromMe')::boolean = false
AND "messageTimestamp" <= ${timestamp}
AND ("status" IS NULL OR "status" = ${status[3]})
`;
}
if (result) { if (result) {
if (result > 0) { if (result > 0) {
@@ -4858,16 +4885,33 @@ export class BaileysStartupService extends ChannelStartupService {
} }
private async updateChatUnreadMessages(remoteJid: string): Promise<number> { private async updateChatUnreadMessages(remoteJid: string): Promise<number> {
const [chat, unreadMessages] = await Promise.all([ const provider = this.configService.get<Database>('DATABASE').PROVIDER;
this.prismaRepository.chat.findFirst({ where: { remoteJid } }),
// Use raw SQL to avoid JSON path issues let unreadMessagesPromise: Promise<number>;
this.prismaRepository.$queryRaw`
if (provider === 'mysql') {
// MySQL version
unreadMessagesPromise = this.prismaRepository.$queryRaw`
SELECT COUNT(*) as count FROM Message
WHERE instanceId = ${this.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.remoteJid')) = ${remoteJid}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.fromMe')) = 'false'
AND status = ${status[3]}
`.then((result: any[]) => Number(result[0]?.count) || 0);
} else {
// PostgreSQL version
unreadMessagesPromise = this.prismaRepository.$queryRaw`
SELECT COUNT(*)::int as count FROM "Message" SELECT COUNT(*)::int as count FROM "Message"
WHERE "instanceId" = ${this.instanceId} WHERE "instanceId" = ${this.instanceId}
AND "key"->>'remoteJid' = ${remoteJid} AND "key"->>'remoteJid' = ${remoteJid}
AND ("key"->>'fromMe')::boolean = false AND ("key"->>'fromMe')::boolean = false
AND "status" = ${status[3]} AND "status" = ${status[3]}
`.then((result: any[]) => result[0]?.count || 0), `.then((result: any[]) => result[0]?.count || 0);
}
const [chat, unreadMessages] = await Promise.all([
this.prismaRepository.chat.findFirst({ where: { remoteJid } }),
unreadMessagesPromise,
]); ]);
if (chat && chat.unreadMessages !== unreadMessages) { if (chat && chat.unreadMessages !== unreadMessages) {
@@ -4879,50 +4923,95 @@ export class BaileysStartupService extends ChannelStartupService {
private async addLabel(labelId: string, instanceId: string, chatId: string) { private async addLabel(labelId: string, instanceId: string, chatId: string) {
const id = cuid(); const id = cuid();
const provider = this.configService.get<Database>('DATABASE').PROVIDER;
await this.prismaRepository.$executeRawUnsafe( if (provider === 'mysql') {
`INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt") // MySQL version - use INSERT ... ON DUPLICATE KEY UPDATE
VALUES ($4, $2, $3, to_jsonb(ARRAY[$1]::text[]), NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid") await this.prismaRepository.$executeRawUnsafe(
DO `INSERT INTO Chat (id, instanceId, remoteJid, labels, createdAt, updatedAt)
UPDATE VALUES (?, ?, ?, JSON_ARRAY(?), NOW(), NOW())
SET "labels" = ( ON DUPLICATE KEY UPDATE
SELECT to_jsonb(array_agg(DISTINCT elem)) labels = JSON_ARRAY_APPEND(
FROM ( COALESCE(labels, JSON_ARRAY()),
SELECT jsonb_array_elements_text("Chat"."labels") AS elem '$',
UNION ?
SELECT $1::text AS elem ),
) sub updatedAt = NOW()`,
), id,
"updatedAt" = NOW();`, instanceId,
labelId, chatId,
instanceId, labelId,
chatId, labelId,
id, );
); } else {
// PostgreSQL version
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) { private async removeLabel(labelId: string, instanceId: string, chatId: string) {
const id = cuid(); const id = cuid();
const provider = this.configService.get<Database>('DATABASE').PROVIDER;
await this.prismaRepository.$executeRawUnsafe( if (provider === 'mysql') {
`INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt") // MySQL version - use INSERT ... ON DUPLICATE KEY UPDATE
VALUES ($4, $2, $3, '[]'::jsonb, NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid") await this.prismaRepository.$executeRawUnsafe(
DO `INSERT INTO Chat (id, instanceId, remoteJid, labels, createdAt, updatedAt)
UPDATE VALUES (?, ?, ?, JSON_ARRAY(), NOW(), NOW())
SET "labels" = COALESCE ( ON DUPLICATE KEY UPDATE
( labels = COALESCE(
SELECT jsonb_agg(elem) JSON_REMOVE(
FROM jsonb_array_elements_text("Chat"."labels") AS elem labels,
WHERE elem <> $1 JSON_UNQUOTE(JSON_SEARCH(labels, 'one', ?))
), ),
'[]'::jsonb JSON_ARRAY()
), ),
"updatedAt" = NOW();`, updatedAt = NOW()`,
labelId, id,
instanceId, instanceId,
chatId, chatId,
id, labelId,
); );
} else {
// PostgreSQL version
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,
);
}
} }
public async baileysOnWhatsapp(jid: string) { public async baileysOnWhatsapp(jid: string) {
@@ -1617,18 +1617,36 @@ export class ChatwootService {
return; return;
} }
// Use raw SQL to avoid JSON path issues const provider = this.configService.get<Database>('DATABASE').PROVIDER;
const result = await this.prismaRepository.$executeRaw` let result: number;
UPDATE "Message"
SET if (provider === 'mysql') {
"chatwootMessageId" = ${chatwootMessageIds.messageId}, // MySQL version
"chatwootConversationId" = ${chatwootMessageIds.conversationId}, result = await this.prismaRepository.$executeRaw`
"chatwootInboxId" = ${chatwootMessageIds.inboxId}, UPDATE Message
"chatwootContactInboxSourceId" = ${chatwootMessageIds.contactInboxSourceId}, SET
"chatwootIsRead" = ${chatwootMessageIds.isRead || false} chatwootMessageId = ${chatwootMessageIds.messageId},
WHERE "instanceId" = ${instance.instanceId} chatwootConversationId = ${chatwootMessageIds.conversationId},
AND "key"->>'id' = ${key.id} chatwootInboxId = ${chatwootMessageIds.inboxId},
`; chatwootContactInboxSourceId = ${chatwootMessageIds.contactInboxSourceId},
chatwootIsRead = ${chatwootMessageIds.isRead || false}
WHERE instanceId = ${instance.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.id')) = ${key.id}
`;
} else {
// PostgreSQL version
result = await this.prismaRepository.$executeRaw`
UPDATE "Message"
SET
"chatwootMessageId" = ${chatwootMessageIds.messageId},
"chatwootConversationId" = ${chatwootMessageIds.conversationId},
"chatwootInboxId" = ${chatwootMessageIds.inboxId},
"chatwootContactInboxSourceId" = ${chatwootMessageIds.contactInboxSourceId},
"chatwootIsRead" = ${chatwootMessageIds.isRead || false}
WHERE "instanceId" = ${instance.instanceId}
AND "key"->>'id' = ${key.id}
`;
}
this.logger.verbose(`Update result: ${result} rows affected`); this.logger.verbose(`Update result: ${result} rows affected`);
@@ -1642,15 +1660,28 @@ export class ChatwootService {
} }
private async getMessageByKeyId(instance: InstanceDto, keyId: string): Promise<MessageModel> { private async getMessageByKeyId(instance: InstanceDto, keyId: string): Promise<MessageModel> {
// Use raw SQL query to avoid JSON path issues with Prisma const provider = this.configService.get<Database>('DATABASE').PROVIDER;
const messages = await this.prismaRepository.$queryRaw` let messages: MessageModel[];
SELECT * FROM "Message"
WHERE "instanceId" = ${instance.instanceId}
AND "key"->>'id' = ${keyId}
LIMIT 1
`;
return (messages as MessageModel[])[0] || null; if (provider === 'mysql') {
// MySQL version
messages = await this.prismaRepository.$queryRaw`
SELECT * FROM Message
WHERE instanceId = ${instance.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.id')) = ${keyId}
LIMIT 1
`;
} else {
// PostgreSQL version
messages = await this.prismaRepository.$queryRaw`
SELECT * FROM "Message"
WHERE "instanceId" = ${instance.instanceId}
AND "key"->>'id' = ${keyId}
LIMIT 1
`;
}
return messages[0] || null;
} }
private async getReplyToIds( private async getReplyToIds(
+118 -54
View File
@@ -9,7 +9,7 @@ import { TypebotService } from '@api/integrations/chatbot/typebot/services/typeb
import { PrismaRepository, Query } from '@api/repository/repository.service'; import { PrismaRepository, Query } from '@api/repository/repository.service';
import { eventManager, waMonitor } from '@api/server.module'; import { eventManager, waMonitor } from '@api/server.module';
import { Events, wa } from '@api/types/wa.types'; import { Events, wa } from '@api/types/wa.types';
import { Auth, Chatwoot, ConfigService, HttpServer, Proxy } from '@config/env.config'; import { Auth, Chatwoot, ConfigService, Database, HttpServer, Proxy } from '@config/env.config';
import { Logger } from '@config/logger.config'; import { Logger } from '@config/logger.config';
import { NotFoundException } from '@exceptions'; import { NotFoundException } from '@exceptions';
import { Contact, Message, Prisma } from '@prisma/client'; import { Contact, Message, Prisma } from '@prisma/client';
@@ -731,63 +731,127 @@ export class ChannelStartupService {
where['remoteJid'] = remoteJid; where['remoteJid'] = remoteJid;
} }
const timestampFilter = const provider = this.configService.get<Database>('DATABASE').PROVIDER;
query?.where?.messageTimestamp?.gte && query?.where?.messageTimestamp?.lte
? Prisma.sql`
AND "Message"."messageTimestamp" >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
AND "Message"."messageTimestamp" <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
: Prisma.sql``;
const limit = query?.take ? Prisma.sql`LIMIT ${query.take}` : Prisma.sql``; const limit = query?.take ? Prisma.sql`LIMIT ${query.take}` : Prisma.sql``;
const offset = query?.skip ? Prisma.sql`OFFSET ${query.skip}` : Prisma.sql``; const offset = query?.skip ? Prisma.sql`OFFSET ${query.skip}` : Prisma.sql``;
const results = await this.prismaRepository.$queryRaw` let results: any[];
WITH rankedMessages AS (
SELECT DISTINCT ON ("Message"."key"->>'remoteJid') if (provider === 'mysql') {
"Contact"."id" as "contactId", // MySQL version
"Message"."key"->>'remoteJid' as "remoteJid", const timestampFilterMysql =
CASE query?.where?.messageTimestamp?.gte && query?.where?.messageTimestamp?.lte
WHEN "Message"."key"->>'remoteJid' LIKE '%@g.us' THEN COALESCE("Chat"."name", "Contact"."pushName") ? Prisma.sql`
ELSE COALESCE("Contact"."pushName", "Message"."pushName") AND Message.messageTimestamp >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
END as "pushName", AND Message.messageTimestamp <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
"Contact"."profilePicUrl", : Prisma.sql``;
COALESCE(
to_timestamp("Message"."messageTimestamp"::double precision), results = await this.prismaRepository.$queryRaw`
"Contact"."updatedAt" SELECT
) as "updatedAt", Contact.id as contactId,
"Chat"."name" as "pushName", JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid')) as remoteJid,
"Chat"."createdAt" as "windowStart",
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
"Chat"."unreadMessages" as "unreadMessages",
CASE WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true ELSE false END as "windowActive",
"Message"."id" AS "lastMessageId",
"Message"."key" AS "lastMessage_key",
CASE CASE
WHEN "Message"."key"->>'fromMe' = 'true' THEN 'Você' WHEN JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid')) LIKE '%@g.us' THEN COALESCE(Chat.name, Contact.pushName)
ELSE "Message"."pushName" ELSE COALESCE(Contact.pushName, Message.pushName)
END AS "lastMessagePushName", END as pushName,
"Message"."participant" AS "lastMessageParticipant", Contact.profilePicUrl,
"Message"."messageType" AS "lastMessageMessageType", COALESCE(
"Message"."message" AS "lastMessageMessage", FROM_UNIXTIME(Message.messageTimestamp),
"Message"."contextInfo" AS "lastMessageContextInfo", Contact.updatedAt
"Message"."source" AS "lastMessageSource", ) as updatedAt,
"Message"."messageTimestamp" AS "lastMessageMessageTimestamp", Chat.name as chatName,
"Message"."instanceId" AS "lastMessageInstanceId", Chat.createdAt as windowStart,
"Message"."sessionId" AS "lastMessageSessionId", DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
"Message"."status" AS "lastMessageStatus" Chat.unreadMessages as unreadMessages,
FROM "Message" CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive,
LEFT JOIN "Contact" ON "Contact"."remoteJid" = "Message"."key"->>'remoteJid' AND "Contact"."instanceId" = "Message"."instanceId" Message.id AS lastMessageId,
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Message"."key"->>'remoteJid' AND "Chat"."instanceId" = "Message"."instanceId" Message.key AS lastMessage_key,
WHERE "Message"."instanceId" = ${this.instanceId} CASE
${remoteJid ? Prisma.sql`AND "Message"."key"->>'remoteJid' = ${remoteJid}` : Prisma.sql``} WHEN JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.fromMe')) = 'true' THEN 'Você'
${timestampFilter} ELSE Message.pushName
ORDER BY "Message"."key"->>'remoteJid', "Message"."messageTimestamp" DESC END AS lastMessagePushName,
) Message.participant AS lastMessageParticipant,
SELECT * FROM rankedMessages Message.messageType AS lastMessageMessageType,
ORDER BY "updatedAt" DESC NULLS LAST Message.message AS lastMessageMessage,
${limit} Message.contextInfo AS lastMessageContextInfo,
${offset}; Message.source AS lastMessageSource,
`; Message.messageTimestamp AS lastMessageMessageTimestamp,
Message.instanceId AS lastMessageInstanceId,
Message.sessionId AS lastMessageSessionId,
Message.status AS lastMessageStatus
FROM Message
LEFT JOIN Contact ON Contact.remoteJid = JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid')) AND Contact.instanceId = Message.instanceId
LEFT JOIN Chat ON Chat.remoteJid = JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid')) AND Chat.instanceId = Message.instanceId
WHERE Message.instanceId = ${this.instanceId}
${remoteJid ? Prisma.sql`AND JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid')) = ${remoteJid}` : Prisma.sql``}
${timestampFilterMysql}
AND Message.messageTimestamp = (
SELECT MAX(m2.messageTimestamp)
FROM Message m2
WHERE JSON_UNQUOTE(JSON_EXTRACT(m2.key, '$.remoteJid')) = JSON_UNQUOTE(JSON_EXTRACT(Message.key, '$.remoteJid'))
AND m2.instanceId = Message.instanceId
)
ORDER BY updatedAt DESC
${limit}
${offset};
`;
} else {
// PostgreSQL version
const timestampFilter =
query?.where?.messageTimestamp?.gte && query?.where?.messageTimestamp?.lte
? Prisma.sql`
AND "Message"."messageTimestamp" >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
AND "Message"."messageTimestamp" <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
: Prisma.sql``;
results = await this.prismaRepository.$queryRaw`
WITH rankedMessages AS (
SELECT DISTINCT ON ("Message"."key"->>'remoteJid')
"Contact"."id" as "contactId",
"Message"."key"->>'remoteJid' as "remoteJid",
CASE
WHEN "Message"."key"->>'remoteJid' LIKE '%@g.us' THEN COALESCE("Chat"."name", "Contact"."pushName")
ELSE COALESCE("Contact"."pushName", "Message"."pushName")
END as "pushName",
"Contact"."profilePicUrl",
COALESCE(
to_timestamp("Message"."messageTimestamp"::double precision),
"Contact"."updatedAt"
) as "updatedAt",
"Chat"."name" as "pushName",
"Chat"."createdAt" as "windowStart",
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
"Chat"."unreadMessages" as "unreadMessages",
CASE WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true ELSE false END as "windowActive",
"Message"."id" AS "lastMessageId",
"Message"."key" AS "lastMessage_key",
CASE
WHEN "Message"."key"->>'fromMe' = 'true' THEN 'Você'
ELSE "Message"."pushName"
END AS "lastMessagePushName",
"Message"."participant" AS "lastMessageParticipant",
"Message"."messageType" AS "lastMessageMessageType",
"Message"."message" AS "lastMessageMessage",
"Message"."contextInfo" AS "lastMessageContextInfo",
"Message"."source" AS "lastMessageSource",
"Message"."messageTimestamp" AS "lastMessageMessageTimestamp",
"Message"."instanceId" AS "lastMessageInstanceId",
"Message"."sessionId" AS "lastMessageSessionId",
"Message"."status" AS "lastMessageStatus"
FROM "Message"
LEFT JOIN "Contact" ON "Contact"."remoteJid" = "Message"."key"->>'remoteJid' AND "Contact"."instanceId" = "Message"."instanceId"
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Message"."key"->>'remoteJid' AND "Chat"."instanceId" = "Message"."instanceId"
WHERE "Message"."instanceId" = ${this.instanceId}
${remoteJid ? Prisma.sql`AND "Message"."key"->>'remoteJid' = ${remoteJid}` : Prisma.sql``}
${timestampFilter}
ORDER BY "Message"."key"->>'remoteJid', "Message"."messageTimestamp" DESC
)
SELECT * FROM rankedMessages
ORDER BY "updatedAt" DESC NULLS LAST
${limit}
${offset};
`;
}
if (results && isArray(results) && results.length > 0) { if (results && isArray(results) && results.length > 0) {
const mappedResults = results.map((contact) => { const mappedResults = results.map((contact) => {