Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"start:prod": "node dist/main",
"dev:server": "tsx watch ./src/main.ts",
"test": "tsx watch ./test/all.test.ts",
"lint": "eslint --fix --ext .ts src",
"lint:check": "eslint --ext .ts src",
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint --fix --ext .ts src",
"lint:check": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .ts src",
"commit": "cz",
"commitlint": "commitlint --edit",
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
Expand Down Expand Up @@ -53,7 +53,7 @@
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
"lint-staged": {
"src/**/*.{ts,js}": [
"eslint --fix"
"sh -c 'ESLINT_USE_FLAT_CONFIG=false eslint --fix'"
],
"src/**/*.ts": [
"sh -c 'tsc --noEmit'"
Expand Down
38 changes: 38 additions & 0 deletions project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "engine",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The $schema path may be incorrect relative to the location of project.json.

Since this project.json is at the repo root and sourceRoot is apps/engine/src, the schema file should likely be referenced as node_modules/nx/schemas/project-schema.json (no ../ segments). The current ../../node_modules/... path would resolve above the repo root and likely break Nx/IDE schema validation. Please verify the actual schema location and update the path accordingly so tooling can validate this config.

"sourceRoot": "apps/engine/src",
"projectType": "application",
"prefix": "engine",
"targets": {
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "npm run dev:server",
"cwd": "apps/engine"
}
},
"build": {
"executor": "nx:run-commands",
"options": {
"command": "npm run build",
"cwd": "apps/engine"
}
},
"db:migrate": {
"executor": "nx:run-commands",
"options": {
"command": "npm run db:deploy",
"cwd": "apps/engine"
}
},
"db:seed": {
"executor": "nx:run-commands",
"options": {
"command": "npm run db:seed",
"cwd": "apps/engine"
}
}
},
"tags": ["type:app", "scope:engine"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ export class BusinessStartupService extends ChannelStartupService {
where: {
instanceId: this.instanceId,
key: {
path: ['id'],
path: '$.id',
equals: key.id,
},
},
Expand Down
88 changes: 61 additions & 27 deletions src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,7 @@ export class BaileysStartupService extends ChannelStartupService {
const savedLabel = labelsRepository.find((l) => l.labelId === label.id);
if (label.deleted && savedLabel) {
await this.prismaRepository.label.delete({
where: { labelId_instanceId: { instanceId: this.instanceId, labelId: label.id } },
where: { id: savedLabel.id },
});
this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name });
return;
Expand All @@ -1835,11 +1835,16 @@ export class BaileysStartupService extends ChannelStartupService {
predefinedId: label.predefinedId,
instanceId: this.instanceId,
};
await this.prismaRepository.label.upsert({
where: { labelId_instanceId: { instanceId: labelData.instanceId, labelId: labelData.labelId } },
update: labelData,
create: labelData,
});
if (savedLabel) {
await this.prismaRepository.label.update({
where: { id: savedLabel.id },
data: labelData,
});
} else {
await this.prismaRepository.label.create({
data: labelData,
});
}
}
}
},
Expand Down Expand Up @@ -3776,7 +3781,7 @@ export class BaileysStartupService extends ChannelStartupService {
if (messageId) {
const isLogicalDeleted = configService.get<Database>('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE;
let message = await this.prismaRepository.message.findFirst({
where: { key: { path: ['id'], equals: messageId } },
where: { key: { path: '$.id', equals: messageId } },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Using string JSON paths ('$.id') in Prisma filters is likely incorrect and may not be supported.

Prisma’s JSON filter path option expects a string[] (e.g. path: ['id']), not a JSONPath-like string ('$.id'). Using the string form is likely to cause type errors or failing queries.

This change appears in multiple places (e.g. fetchMessages, ChatwootService, and other findFirst calls). Either keep the array form (['id'], ['fromMe'], etc., which is portable across databases) or, if you need MySQL-specific JSON semantics, use $queryRaw/$executeRaw with explicit SQL as in updateMessagesReadedByTimestamp. Otherwise these filters may not match any messages at runtime.

});
if (isLogicalDeleted) {
if (!message) return response;
Expand Down Expand Up @@ -4196,7 +4201,7 @@ export class BaileysStartupService extends ChannelStartupService {
const messageId = messageSent.message?.protocolMessage?.key?.id;
if (messageId && this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE) {
let message = await this.prismaRepository.message.findFirst({
where: { key: { path: ['id'], equals: messageId } },
where: { key: { path: '$.id', equals: messageId } },
});
if (!message) throw new NotFoundException('Message not found');

Expand Down Expand Up @@ -4734,6 +4739,26 @@ export class BaileysStartupService extends ChannelStartupService {
private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise<number> {
if (timestamp === undefined || timestamp === null) return 0;

const dbProvider = String(this.configService.get<Database>('DATABASE')?.PROVIDER || '').toLowerCase();

if (dbProvider === 'mysql') {
const result = await this.prismaRepository.$executeRaw`
UPDATE \`Message\`
SET \`status\` = ${status[4]}
WHERE \`instanceId\` = ${this.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.remoteJid')) = ${remoteJid}
AND JSON_EXTRACT(\`key\`, '$.fromMe') = false
AND \`messageTimestamp\` <= ${timestamp}
AND (\`status\` IS NULL OR \`status\` = ${status[3]})
`;

if (result && result > 0) {
this.updateChatUnreadMessages(remoteJid);
}

return result || 0;
}

// Use raw SQL to avoid JSON path issues
const result = await this.prismaRepository.$executeRaw`
UPDATE "Message"
Expand All @@ -4757,16 +4782,25 @@ export class BaileysStartupService extends ChannelStartupService {
}

private async updateChatUnreadMessages(remoteJid: string): Promise<number> {
const dbProvider = String(this.configService.get<Database>('DATABASE')?.PROVIDER || '').toLowerCase();

const [chat, unreadMessages] = await Promise.all([
this.prismaRepository.chat.findFirst({ where: { remoteJid } }),
// Use raw SQL to avoid JSON path issues
this.prismaRepository.$queryRaw`
SELECT COUNT(*)::int as count FROM "Message"
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'remoteJid' = ${remoteJid}
AND ("key"->>'fromMe')::boolean = false
AND "status" = ${status[3]}
`.then((result: any[]) => result[0]?.count || 0),
dbProvider === 'mysql'
? this.prismaRepository.$queryRaw`
SELECT COUNT(*) as count FROM \`Message\`
WHERE \`instanceId\` = ${this.instanceId}
AND JSON_UNQUOTE(JSON_EXTRACT(\`key\`, '$.remoteJid')) = ${remoteJid}
AND JSON_EXTRACT(\`key\`, '$.fromMe') = false
AND \`status\` = ${status[3]}
`.then((result: any[]) => Number(result?.[0]?.count || 0))
: this.prismaRepository.$queryRaw`
SELECT COUNT(*)::int as count FROM "Message"
WHERE "instanceId" = ${this.instanceId}
AND "key"->>'remoteJid' = ${remoteJid}
AND ("key"->>'fromMe')::boolean = false
AND "status" = ${status[3]}
`.then((result: any[]) => result[0]?.count || 0),
]);

if (chat && chat.unreadMessages !== unreadMessages) {
Expand Down Expand Up @@ -5013,7 +5047,7 @@ export class BaileysStartupService extends ChannelStartupService {
}
}

public async fetchMessages(query: Query<Message>) {
public async fetchMessages(query: Query<Message>): Promise<any> {
const keyFilters = query?.where?.key as ExtendedIMessageKey;

const timestampFilter = {};
Comment on lines -5016 to 5019
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Loosening fetchMessages to return Promise<any> weakens type safety and downstream usage.

This change hides the actual result shape from callers and removes compile-time checks on returned fields. If you need to support multiple result shapes (e.g., DB-specific raw results), prefer a typed union or a dedicated interface/DTO over any to keep flexibility without losing type safety.

Suggested implementation:

  public async fetchMessages(query: Query<Message>): Promise<Message[]> {

If fetchMessages can actually return shapes other than Message[] (e.g., different DB-specific raw formats), you should:

  1. Introduce a dedicated return type, for example:
    type FetchMessagesResult = Message[] | MysqlRawMessageRow[] | PostgresRawMessageRow[];
  2. Replace the method signature with:
    public async fetchMessages(query: Query<Message>): Promise<FetchMessagesResult> { ... }
  3. Ensure the implementation of fetchMessages constructs and returns instances matching the chosen union/interface so that call sites can rely on proper type narrowing instead of any.

Expand All @@ -5034,13 +5068,13 @@ export class BaileysStartupService extends ChannelStartupService {
messageType: query?.where?.messageType,
...timestampFilter,
AND: [
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
keyFilters?.id ? { key: { path: '$.id', equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: '$.fromMe', equals: keyFilters?.fromMe } } : {},
keyFilters?.participant ? { key: { path: '$.participant', equals: keyFilters?.participant } } : {},
{
OR: [
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
keyFilters?.remoteJidAlt ? { key: { path: ['remoteJidAlt'], equals: keyFilters?.remoteJidAlt } } : {},
keyFilters?.remoteJid ? { key: { path: '$.remoteJid', equals: keyFilters?.remoteJid } } : {},
keyFilters?.remoteJidAlt ? { key: { path: '$.remoteJidAlt', equals: keyFilters?.remoteJidAlt } } : {},
],
},
],
Expand All @@ -5063,13 +5097,13 @@ export class BaileysStartupService extends ChannelStartupService {
messageType: query?.where?.messageType,
...timestampFilter,
AND: [
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
keyFilters?.id ? { key: { path: '$.id', equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: '$.fromMe', equals: keyFilters?.fromMe } } : {},
keyFilters?.participant ? { key: { path: '$.participant', equals: keyFilters?.participant } } : {},
{
OR: [
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
keyFilters?.remoteJidAlt ? { key: { path: ['remoteJidAlt'], equals: keyFilters?.remoteJidAlt } } : {},
keyFilters?.remoteJid ? { key: { path: '$.remoteJid', equals: keyFilters?.remoteJid } } : {},
keyFilters?.remoteJidAlt ? { key: { path: '$.remoteJidAlt', equals: keyFilters?.remoteJidAlt } } : {},
],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1546,7 +1546,7 @@ export class ChatwootService {
const lastMessage = await this.prismaRepository.message.findFirst({
where: {
key: {
path: ['fromMe'],
path: '$.fromMe',
equals: false,
},
instanceId: instance.instanceId,
Expand Down Expand Up @@ -1576,7 +1576,7 @@ export class ChatwootService {
where: {
instanceId: instance.instanceId,
key: {
path: ['id'],
path: '$.id',
equals: key.id,
},
},
Expand Down Expand Up @@ -2025,7 +2025,7 @@ export class ChatwootService {
quotedMsg = await this.prismaRepository.message.findFirst({
where: {
key: {
path: ['id'],
path: '$.id',
equals: quotedId,
},
chatwootMessageId: {
Expand Down Expand Up @@ -2337,7 +2337,7 @@ export class ChatwootService {
await this.prismaRepository.message.deleteMany({
where: {
key: {
path: ['id'],
path: '$.id',
equals: body.key.id,
},
instanceId: instance.instanceId,
Expand Down
16 changes: 8 additions & 8 deletions src/api/services/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,10 @@ export class ChannelStartupService {
messageType: query?.where?.messageType,
...timestampFilter,
AND: [
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
keyFilters?.participants ? { key: { path: ['participants'], equals: keyFilters?.participants } } : {},
keyFilters?.id ? { key: { path: '$.id', equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: '$.fromMe', equals: keyFilters?.fromMe } } : {},
keyFilters?.remoteJid ? { key: { path: '$.remoteJid', equals: keyFilters?.remoteJid } } : {},
keyFilters?.participants ? { key: { path: '$.participants', equals: keyFilters?.participants } } : {},
],
},
});
Expand All @@ -647,10 +647,10 @@ export class ChannelStartupService {
messageType: query?.where?.messageType,
...timestampFilter,
AND: [
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
keyFilters?.participants ? { key: { path: ['participants'], equals: keyFilters?.participants } } : {},
keyFilters?.id ? { key: { path: '$.id', equals: keyFilters?.id } } : {},
keyFilters?.fromMe ? { key: { path: '$.fromMe', equals: keyFilters?.fromMe } } : {},
keyFilters?.remoteJid ? { key: { path: '$.remoteJid', equals: keyFilters?.remoteJid } } : {},
keyFilters?.participants ? { key: { path: '$.participants', equals: keyFilters?.participants } } : {},
],
},
orderBy: {
Expand Down
13 changes: 4 additions & 9 deletions src/utils/onWhatsappCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,9 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
// Ordena os JIDs para garantir consistΓͺncia na string final
const sortedJidOptions = [...finalJidOptions].sort();
const newJidOptionsString = sortedJidOptions.join(',');
const newLid = item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null;

const dataPayload = {
remoteJid: remoteJid,
jidOptions: newJidOptionsString,
lid: newLid,
};

// 4. Decide entre Criar ou Atualizar
Expand All @@ -142,9 +139,7 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
: '';

const isDataSame =
existingRecord.remoteJid === dataPayload.remoteJid &&
existingJidOptionsString === dataPayload.jidOptions &&
existingRecord.lid === dataPayload.lid;
existingRecord.remoteJid === dataPayload.remoteJid && existingJidOptionsString === dataPayload.jidOptions;

if (isDataSame) {
logger.verbose(`[saveOnWhatsappCache] Data for ${remoteJid} is already up-to-date. Skipping update.`);
Expand All @@ -153,7 +148,7 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {

// Os dados sΓ£o diferentes, entΓ£o atualiza
logger.verbose(
`[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
`[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}`,
);
await prismaRepository.isOnWhatsapp.update({
where: { id: existingRecord.id },
Expand All @@ -162,7 +157,7 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
} else {
// Cria nova entrada
logger.verbose(
`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}`,
);
await prismaRepository.isOnWhatsapp.create({
data: dataPayload,
Expand Down Expand Up @@ -203,7 +198,7 @@ export async function getOnWhatsappCache(remoteJids: string[]) {
remoteJid: item.remoteJid,
number: item.remoteJid.split('@')[0],
jidOptions: item.jidOptions.split(','),
lid: item.lid,
lid: (item as any).lid || (item.remoteJid.includes('@lid') ? 'lid' : undefined),
}));
}

Expand Down