Skip to content

issue fixed#2487

Open
shajandevz wants to merge 2 commits intoEvolutionAPI:mainfrom
shajandevz:main
Open

issue fixed#2487
shajandevz wants to merge 2 commits intoEvolutionAPI:mainfrom
shajandevz:main

Conversation

@shajandevz
Copy link
Copy Markdown

@shajandevz shajandevz commented Mar 28, 2026

📋 Description

🔗 Related Issue

Closes #(issue_number)

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

Summary by Sourcery

Align WhatsApp message and label persistence with database-specific JSON handling and improve tooling configuration for the engine app.

Bug Fixes:

  • Fix label deletion and updates by using the persisted label primary key instead of the composite labelId/instanceId key.
  • Correct Prisma JSON path filters for message keys to use string JSON paths, avoiding JSON query mismatches across providers.
  • Ensure on-whatsapp cache comparison logic no longer depends on a stored lid field and instead derives lid when reading, preventing stale or inconsistent cache records.
  • Resolve Chatwoot and WhatsApp Business integrations failing to find or delete messages by fixing JSON key path usage in Prisma queries.

Enhancements:

  • Add MySQL-specific raw SQL for updating message read status and unread counts while keeping the existing PostgreSQL path, improving cross-database compatibility and performance.
  • Explicitly type the fetchMessages method return to clarify its API contract.

Build:

  • Update lint and lint-staged scripts to disable ESLint flat config usage explicitly for compatibility with the current configuration.
  • Add an Nx project configuration for the engine app, wiring serve, build, and database commands into the workspace tooling.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 28, 2026

Reviewer's Guide

Adjusts label persistence to use primary keys, fixes JSON path usage for Prisma queries, adds MySQL-specific handling for message read/unread updates, simplifies onWhatsapp cache lid handling, ensures consistent JSON path filters across services, tweaks lint scripts to disable ESLint flat config, and introduces an Nx project.json for the engine app.

Class diagram for updated WhatsApp services and cache utility

classDiagram
  class ChannelStartupService {
    +fetchMessages(query: Query_Message_) Promise_any_
  }

  class BaileysStartupService {
    +fetchMessages(query: Query_Message_) Promise_any_
    -updateMessagesReadedByTimestamp(remoteJid: string, timestamp: number) Promise_number_
    -updateChatUnreadMessages(remoteJid: string) Promise_number_
  }

  class BusinessStartupService {
    +handleIncomingMessage(key: any, instanceId: string) void
  }

  class ChatwootService {
    +syncLastIncomingMessage(instance: any) Promise_void_
    +linkQuotedMessage(quotedId: string, instanceId: string) Promise_void_
    +deleteMessagesByKey(body: any, instance: any) Promise_void_
  }

  class OnWhatsappCacheUtil {
    <<utility>>
    +saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams_array_) Promise_void_
    +getOnWhatsappCache(remoteJids: string_array_) Promise_any_array_
  }

  ChannelStartupService <|-- BaileysStartupService
  ChannelStartupService <|-- BusinessStartupService

  BaileysStartupService ..> OnWhatsappCacheUtil : uses
  ChatwootService ..> BaileysStartupService : shares Message querying
  BusinessStartupService ..> BaileysStartupService : shares Message querying
Loading

File-Level Changes

Change Details Files
Fix label create/update/delete logic to rely on existing label primary keys instead of composite labelId/instanceId keys.
  • Delete labels using the stored label row id when a label is marked as deleted.
  • Replace label upsert with explicit update-by-id when a label exists and create when it does not.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Normalize Prisma JSON path filters from array syntax to string JSONPath across message and chat queries.
  • Update multiple message findFirst and query filters to use path '$.id', '$.fromMe', '$.participant', '$.remoteJid', '$.remoteJidAlt' instead of array path notation.
  • Apply the same JSON path changes in Chatwoot- and Meta-related message queries and in generic channel fetchMessages helpers.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/services/channel.service.ts
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
src/api/integrations/channel/meta/whatsapp.business.service.ts
Add database-provider-aware SQL for marking messages read and computing unread counts, with dedicated MySQL and PostgreSQL paths.
  • Inject DATABASE.PROVIDER-based branching in updateMessagesReadedByTimestamp to run a MySQL-flavored UPDATE on Message with JSON_EXTRACT/JSON_UNQUOTE or retain existing Postgres JSON operator SQL.
  • Update updateChatUnreadMessages to use provider-specific COUNT(*) query: MySQL JSON_EXTRACT and quoting vs existing Postgres JSON operators, keeping unread count in sync with chat records.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Simplify onWhatsapp cache lid persistence while still exposing lid in the returned DTO when present or implied.
  • Stop computing and storing lid on save, and remove lid from cache equality comparison and log messages.
  • Derive lid on read using the record’s lid when present or by inspecting remoteJid for '@lid' and mapping to 'lid' in the returned structure.
src/utils/onWhatsappCache.ts
Adjust lint-related npm scripts and lint-staged hooks to work with non-flat ESLint configuration and shell context.
  • Prefix lint and lint:check npm scripts with ESLINT_USE_FLAT_CONFIG=false.
  • Wrap lint-staged ESLint command in sh -c and set ESLINT_USE_FLAT_CONFIG=false inside it.
package.json
Register the engine app as an Nx project with standard targets for serving, building, and database operations.
  • Introduce project.json configuring name, schema, sourceRoot, tags, and nx:run-commands targets that proxy to existing npm scripts for dev server, build, db:migrate, and db:seed.
project.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The JSON path filters you changed from ['id']/['fromMe'] to '$.id'/'$.fromMe' will behave differently across providers (e.g., Postgres vs MySQL); consider branching these filters by DATABASE.PROVIDER or using provider-agnostic queries as you did with the raw SQL paths to avoid breaking non-MySQL setups.
  • Changing fetchMessages to return Promise<any> loses useful typing information; it would be better to return a concrete type (e.g., Promise<Message[]> or a specific DTO) to preserve type safety for callers.
  • In onWhatsappCache, the use of (item as any).lid suggests the TypeScript types for the isOnWhatsapp model are now out of sync with the actual data; updating the model/interface to reflect the new behavior (or explicitly documenting the computed lid) would avoid the need for any and potential runtime confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The JSON `path` filters you changed from `['id']`/`['fromMe']` to `'$.id'`/`'$.fromMe'` will behave differently across providers (e.g., Postgres vs MySQL); consider branching these filters by `DATABASE.PROVIDER` or using provider-agnostic queries as you did with the raw SQL paths to avoid breaking non-MySQL setups.
- Changing `fetchMessages` to return `Promise<any>` loses useful typing information; it would be better to return a concrete type (e.g., `Promise<Message[]>` or a specific DTO) to preserve type safety for callers.
- In `onWhatsappCache`, the use of `(item as any).lid` suggests the TypeScript types for the `isOnWhatsapp` model are now out of sync with the actual data; updating the model/interface to reflect the new behavior (or explicitly documenting the computed `lid`) would avoid the need for `any` and potential runtime confusion.

## Individual Comments

### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="3784" />
<code_context>
           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 } },
           });
           if (isLogicalDeleted) {
</code_context>
<issue_to_address>
**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.
</issue_to_address>

### Comment 2
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="5016-5019" />
<code_context>
     }
   }

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

</code_context>
<issue_to_address>
**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:

```typescript
  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`.
</issue_to_address>

### Comment 3
<location path="project.json" line_range="3" />
<code_context>
+{
+  "name": "engine",
+  "$schema": "../../node_modules/nx/schemas/project-schema.json",
+  "sourceRoot": "apps/engine/src",
+  "projectType": "application",
</code_context>
<issue_to_address>
**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.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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.

Comment on lines -5016 to 5019
public async fetchMessages(query: Query<Message>) {
public async fetchMessages(query: Query<Message>): Promise<any> {
const keyFilters = query?.where?.key as ExtendedIMessageKey;

const timestampFilter = {};
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.

@@ -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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant