Skip to content

Conversation

@augustolima1
Copy link

@augustolima1 augustolima1 commented Dec 23, 2025

Esta PR corrige problemas de compatibilidade com MySQL 8.0 no Evolution API.

Arquivos alterados:

  1. prisma/mysql-migrations/20250918183910_add_kafka_integration/migration.sql

    • Removido DROP COLUMN lid que estava deletando indevidamente a coluna lid da tabela IsOnWhatsapp, criada pela migration 20250613143000_add_lid_column_to_is_onwhatsapp.
  2. prisma/mysql-schema.prisma

    • Adicionado campo lid String? @db.VarChar(100) no modelo IsOnWhatsapp para sincronizar o schema com a migration existente que já cria essa coluna no banco.
  3. src/api/services/channel.service.ts

    • Função fetchChats(): Adicionada versão MySQL da query RAW, mantendo a versão PostgreSQL original.
    • Substitui DISTINCT ON, ->>'remoteJid', to_timestamp(), INTERVAL '24 hours' por equivalentes MySQL.
  4. src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

    • getMessage(): Versões PostgreSQL/MySQL para busca por key->>'id'.
    • Busca em messages.update: Versões PostgreSQL/MySQL.
    • updateMessagesReadedByTimestamp(): Versões PostgreSQL/MySQL para operador JSON e cast ::boolean.
    • updateChatUnreadMessages(): Versões PostgreSQL/MySQL para COUNT(*)::int e operadores JSON.
    • addLabel(): Versões PostgreSQL/MySQL para to_jsonb, jsonb_array_elements_text.
    • removeLabel(): Versões PostgreSQL/MySQL para jsonb_agg, '[]'::jsonb.
  5. src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

    • updateChatwootMessageId(): Versões PostgreSQL/MySQL para operador key->>'id'.
    • getMessageByKeyId(): Versões PostgreSQL/MySQL para operador key->>'id'.

Summary by Sourcery

Ensure database-agnostic behavior for WhatsApp/channel and Chatwoot integrations while preserving the IsOnWhatsapp lid column in MySQL migrations and schema.

Bug Fixes:

  • Prevent accidental removal of the lid column from the IsOnWhatsapp table in the MySQL Kafka integration migration.
  • Align the Prisma MySQL schema with the existing lid column on IsOnWhatsapp to avoid schema drift and runtime errors.
  • Fix raw message, chat, and Chatwoot queries so they work correctly on both PostgreSQL and MySQL providers, including JSON access and timestamp handling.

Enhancements:

  • Add database-provider-aware branches to WhatsApp Baileys, channel listing, and Chatwoot services to support both PostgreSQL and MySQL with appropriate SQL syntax and JSON functions.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 23, 2025

Reviewer's Guide

Adds MySQL 8-compatible behavior for JSON-based raw queries and preserves the IsOnWhatsapp.lid column by aligning Prisma schema and MySQL migration; introduces DB-provider branching to support both PostgreSQL and MySQL for key JSON operations in messaging and Chatwoot integrations.

Sequence diagram for fetchChats with PostgreSQL/MySQL branching

sequenceDiagram
  actor Client
  participant ChannelStartupService
  participant ConfigService
  participant PrismaRepository
  participant PostgreSQL
  participant MySQL

  Client->>ChannelStartupService: fetchChats(query, remoteJid)
  ChannelStartupService->>ConfigService: get(DATABASE)
  ConfigService-->>ChannelStartupService: Database{PROVIDER}
  alt provider == mysql
    ChannelStartupService->>PrismaRepository: $queryRaw(MySQL JSON query)
    PrismaRepository->>MySQL: execute SELECT with JSON_EXTRACT
    MySQL-->>PrismaRepository: rows
  else provider != mysql (PostgreSQL)
    ChannelStartupService->>PrismaRepository: $queryRaw(PostgreSQL JSON query)
    PrismaRepository->>PostgreSQL: execute WITH rankedMessages
    PostgreSQL-->>PrismaRepository: rows
  end
  PrismaRepository-->>ChannelStartupService: results
  ChannelStartupService-->>Client: mapped chat list
Loading

Sequence diagram for label management with DB-specific raw SQL

sequenceDiagram
  actor Client
  participant BaileysStartupService
  participant ConfigService
  participant PrismaRepository
  participant PostgreSQL
  participant MySQL

  Client->>BaileysStartupService: addLabel(labelId, instanceId, chatId)
  BaileysStartupService->>ConfigService: get(DATABASE)
  ConfigService-->>BaileysStartupService: Database{PROVIDER}
  alt provider == mysql
    BaileysStartupService->>PrismaRepository: $executeRawUnsafe(INSERT ... ON DUPLICATE KEY UPDATE, JSON_ARRAY/JSON_ARRAY_APPEND)
    PrismaRepository->>MySQL: execute SQL
    MySQL-->>PrismaRepository: affected rows
  else provider != mysql (PostgreSQL)
    BaileysStartupService->>PrismaRepository: $executeRawUnsafe(INSERT ... ON CONFLICT, to_jsonb/jsonb_array_elements_text)
    PrismaRepository->>PostgreSQL: execute SQL
    PostgreSQL-->>PrismaRepository: affected rows
  end
  PrismaRepository-->>BaileysStartupService: result
  BaileysStartupService-->>Client: void
Loading

ER diagram for IsOnWhatsapp with preserved lid column

erDiagram
  IsOnWhatsapp {
    int id PK
    string remoteJid
    string lid
    datetime createdAt
    datetime updatedAt
  }

  %% The PR aligns Prisma schema with existing MySQL migration by keeping lid and adjusting timestamps
Loading

Class diagram for updated messaging and Chatwoot services with DB provider branching

classDiagram
  class ConfigService {
    +get(name string) Database
  }

  class Database {
    +PROVIDER string
  }

  class PrismaRepository {
    +$queryRaw(query any) Promise~any[]~
    +$executeRaw(query any) Promise~number~
    +$executeRawUnsafe(query string, params any) Promise~number~
    +chat_findFirst(where any) Promise~Chat~
  }

  class ChannelStartupService {
    -configService ConfigService
    -prismaRepository PrismaRepository
    -instanceId string
    +fetchChats(query any, remoteJid string) Promise~any[]~
  }

  class BaileysStartupService {
    -configService ConfigService
    -prismaRepository PrismaRepository
    -instanceId string
    +getMessage(key IMessageKey, full boolean) Promise~IWebMessageInfo~
    +updateMessagesReadedByTimestamp(remoteJid string, timestamp number) Promise~number~
    +updateChatUnreadMessages(remoteJid string) Promise~number~
    +addLabel(labelId string, instanceId string, chatId string) Promise~void~
    +removeLabel(labelId string, instanceId string, chatId string) Promise~void~
  }

  class ChatwootService {
    -configService ConfigService
    -prismaRepository PrismaRepository
    +updateChatwootMessageId(instance InstanceDto, key IMessageKey, chatwootMessageIds any) Promise~void~
    +getMessageByKeyId(instance InstanceDto, keyId string) Promise~MessageModel~
  }

  class IMessageKey {
    +id string
  }

  class IWebMessageInfo
  class Chat
  class InstanceDto {
    +instanceId string
  }

  class MessageModel

  ChannelStartupService --> ConfigService : uses
  ChannelStartupService --> PrismaRepository : uses

  BaileysStartupService --> ConfigService : uses
  BaileysStartupService --> PrismaRepository : uses

  ChatwootService --> ConfigService : uses
  ChatwootService --> PrismaRepository : uses

  BaileysStartupService --> IMessageKey : parameter
  ChatwootService --> IMessageKey : parameter
  ChatwootService --> InstanceDto : parameter
  ChannelStartupService --> Chat : reads
  ChatwootService --> MessageModel : returns
  BaileysStartupService --> IWebMessageInfo : returns
Loading

File-Level Changes

Change Details Files
Preserve IsOnWhatsapp.lid column and sync Prisma MySQL schema with existing migrations.
  • Remove DROP COLUMN lid from the IsOnWhatsapp alteration in the Kafka integration MySQL migration so the existing lid column is not deleted.
  • Add optional lid String? @db.VarChar(100) to the IsOnWhatsapp model in the MySQL Prisma schema to match the previously created column.
prisma/mysql-migrations/20250918183910_add_kafka_integration/migration.sql
prisma/mysql-schema.prisma
Introduce DB-provider-aware raw SQL for fetching chats to support both PostgreSQL and MySQL.
  • Read DATABASE.PROVIDER from config in fetchChats to branch query behavior.
  • Keep the original PostgreSQL CTE using DISTINCT ON and JSON operators as the Postgres branch, still supporting timestamp filters, pagination and ordering.
  • Add a MySQL-specific raw query that replaces DISTINCT ON with a correlated subquery on MAX(messageTimestamp), uses JSON_EXTRACT/JSON_UNQUOTE instead of ->>, and uses FROM_UNIXTIME and DATE_ADD instead of to_timestamp and INTERVAL syntax.
  • Apply provider-specific timestamp filters and ensure limit/offset are applied in both branches.
src/api/services/channel.service.ts
Make Baileys WhatsApp integration JSON key access and update logic compatible with both MySQL and PostgreSQL.
  • Update getMessage and the messages.update path to branch by DATABASE.PROVIDER, using JSON_EXTRACT/JSON_UNQUOTE on key->id for MySQL and preserving key->>'id' for PostgreSQL, adding LIMIT 1 for both.
  • Change updateMessagesReadedByTimestamp to use provider-specific raw UPDATEs: for MySQL, use JSON_EXTRACT/JSON_UNQUOTE on key.remoteJid and key.fromMe (string comparison) and standard columns; for PostgreSQL, keep JSON operator and ::boolean cast.
  • Refactor updateChatUnreadMessages to compute unreadMessages via provider-specific COUNT queries, using JSON_EXTRACT/JSON_UNQUOTE in MySQL and COUNT(*)::int plus JSON operators and ::boolean in PostgreSQL, while keeping the existing parallel fetch with chat.findFirst.
  • Replace the PostgreSQL-only label add/remove raw SQL with provider-aware implementations: in MySQL, use INSERT ... ON DUPLICATE KEY UPDATE with JSON_ARRAY/JSON_ARRAY_APPEND/JSON_REMOVE/JSON_SEARCH, and in PostgreSQL retain the jsonb-based on-conflict upserts that dedupe labels or filter them out.
  • Ensure all new branches share the same behavioral intent (upsert Chat rows, manage labels JSON array, update statuses) across DBs.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Enable Chatwoot integration to work on both PostgreSQL and MySQL when querying/updating by JSON key id.
  • Update updateChatwootMessageId to branch by DATABASE.PROVIDER, using a MySQL UPDATE with JSON_EXTRACT/JSON_UNQUOTE on key.id, and preserving the existing PostgreSQL UPDATE using key->>'id'.
  • Update getMessageByKeyId to run provider-specific SELECT: in MySQL, use JSON_EXTRACT/JSON_UNQUOTE on key.id; in PostgreSQL, keep key->>'id', both with LIMIT 1 and returning the first result or null.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

Possibly linked issues

  • #Queries Raw Postgres: They address the same MySQL incompatibility from PostgreSQL-style RAW JSON queries in the specified service files.

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
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 4 issues, and left some high level feedback:

  • There is a lot of repeated const provider = this.configService.get<Database>('DATABASE').PROVIDER / MySQL vs PostgreSQL branching logic across services; consider extracting small helper utilities (e.g. isMySQL() and JSON accessor helpers) to centralize this decision and avoid duplication and drift.
  • The MySQL addLabel implementation uses JSON_ARRAY_APPEND without a DISTINCT step, while the PostgreSQL version explicitly deduplicates labels with array_agg(DISTINCT elem); if label uniqueness is important, you may want to align the MySQL behavior or enforce uniqueness at another layer.
  • In the MySQL removeLabel implementation, JSON_SEARCH(labels, 'one', ?) can return NULL, which will make JSON_REMOVE fail with an invalid path; consider guarding for a NULL search result (e.g. conditionally applying JSON_REMOVE only when a path is found).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There is a lot of repeated `const provider = this.configService.get<Database>('DATABASE').PROVIDER` / MySQL vs PostgreSQL branching logic across services; consider extracting small helper utilities (e.g. `isMySQL()` and JSON accessor helpers) to centralize this decision and avoid duplication and drift.
- The MySQL `addLabel` implementation uses `JSON_ARRAY_APPEND` without a DISTINCT step, while the PostgreSQL version explicitly deduplicates labels with `array_agg(DISTINCT elem)`; if label uniqueness is important, you may want to align the MySQL behavior or enforce uniqueness at another layer.
- In the MySQL `removeLabel` implementation, `JSON_SEARCH(labels, 'one', ?)` can return NULL, which will make `JSON_REMOVE` fail with an invalid path; consider guarding for a NULL search result (e.g. conditionally applying `JSON_REMOVE` only when a path is found).

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:529-538` </location>
<code_context>
+
+      let webMessageInfo: proto.IWebMessageInfo[];
+
+      if (provider === 'mysql') {
+        // MySQL version
+        webMessageInfo = (await this.prismaRepository.$queryRaw`
</code_context>

<issue_to_address>
**issue (bug_risk):** MySQL label insertion does not deduplicate labels like the PostgreSQL version does.

In Postgres, `labels` is built with `array_agg(DISTINCT elem)`, so each label appears at most once per chat. In MySQL, `JSON_ARRAY_APPEND(COALESCE(labels, JSON_ARRAY()), '$', ?)` can add the same `labelId` multiple times, changing the semantics from a set to a multiset across providers. This mismatch can cause subtle bugs if callers assume uniqueness. Please add a deduplication step for MySQL (e.g., via `JSON_TABLE` or a normalization function) so both backends behave consistently.
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4782-4791` </location>
<code_context>
+
+    if (provider === 'mysql') {
+      // MySQL version - use INSERT ... ON DUPLICATE KEY UPDATE
+      await this.prismaRepository.$executeRawUnsafe(
+        `INSERT INTO Chat (id, instanceId, remoteJid, labels, createdAt, updatedAt)
+         VALUES (?, ?, ?, JSON_ARRAY(), NOW(), NOW())
+         ON DUPLICATE KEY UPDATE
+           labels = COALESCE(
+             JSON_REMOVE(
+               labels,
</code_context>

<issue_to_address>
**issue (bug_risk):** MySQL label removal can wipe the entire labels array when the label is not present.

In the MySQL branch, if `JSON_SEARCH` doesn’t find the label, it returns `NULL`. `JSON_REMOVE(labels, NULL)` then evaluates to `NULL`, so `COALESCE(NULL, JSON_ARRAY())` resets `labels` to an empty array. This turns a no-op removal into data loss.

Consider restructuring so `JSON_REMOVE` is only called when `JSON_SEARCH` finds a match (e.g., via a `CASE` or subquery), and otherwise `labels` is left unchanged.
</issue_to_address>

### Comment 3
<location> `src/api/services/channel.service.ts:740-749` </location>
<code_context>
+
+      let webMessageInfo: proto.IWebMessageInfo[];
+
+      if (provider === 'mysql') {
+        // MySQL version
+        webMessageInfo = (await this.prismaRepository.$queryRaw`
</code_context>

<issue_to_address>
**issue (bug_risk):** MySQL conversation listing uses a different "last message" selection semantics than PostgreSQL when applying timestamp filters.

The PostgreSQL query returns the last message *within* the timestamp window per `remoteJid` via `DISTINCT ON` + `ORDER BY ... messageTimestamp DESC`. In contrast, the MySQL query filters by timestamp only in the outer `WHERE`, but selects the last message per chat via a correlated subquery on `MAX(m2.messageTimestamp)` without any timestamp constraint.

This means if the most recent message for a chat is outside the window, but older messages are inside, that chat is excluded in MySQL while included in PostgreSQL (using the last in-range message). To align behavior, the correlated subquery in MySQL should also apply the timestamp filter, or the query should be restructured to emulate `DISTINCT ON` within the time window.
</issue_to_address>

### Comment 4
<location> `src/api/services/channel.service.ts:764-767` </location>
<code_context>
+            Contact.updatedAt
+          ) as updatedAt,
+          Chat.name as chatName,
+          Chat.createdAt as windowStart,
+          DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
+          Chat.unreadMessages as unreadMessages,
+          CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive,
+          Message.id AS lastMessageId,
+          Message.key AS lastMessage_key,
</code_context>

<issue_to_address>
**suggestion (bug_risk):** MySQL `windowActive` is returned as 1/0 instead of boolean, which may diverge from expectations.

In Postgres this column is a boolean expression, but in MySQL it’s `1`/`0`:

```sql
CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive
```

If the TypeScript layer expects a `boolean`, this mismatch can cause subtle issues (strict equality, serialization, validation). Please update the MySQL query to return `TRUE`/`FALSE` or cast to `BOOLEAN` so both backends expose the same type.

```suggestion
          DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
          Chat.unreadMessages as unreadMessages,
          CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN TRUE ELSE FALSE END as windowActive,
          Message.id AS lastMessageId,
```
</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.

Comment on lines +529 to +538
if (provider === 'mysql') {
// 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
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): MySQL label insertion does not deduplicate labels like the PostgreSQL version does.

In Postgres, labels is built with array_agg(DISTINCT elem), so each label appears at most once per chat. In MySQL, JSON_ARRAY_APPEND(COALESCE(labels, JSON_ARRAY()), '$', ?) can add the same labelId multiple times, changing the semantics from a set to a multiset across providers. This mismatch can cause subtle bugs if callers assume uniqueness. Please add a deduplication step for MySQL (e.g., via JSON_TABLE or a normalization function) so both backends behave consistently.

Comment on lines +4782 to 4791
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 > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): MySQL label removal can wipe the entire labels array when the label is not present.

In the MySQL branch, if JSON_SEARCH doesn’t find the label, it returns NULL. JSON_REMOVE(labels, NULL) then evaluates to NULL, so COALESCE(NULL, JSON_ARRAY()) resets labels to an empty array. This turns a no-op removal into data loss.

Consider restructuring so JSON_REMOVE is only called when JSON_SEARCH finds a match (e.g., via a CASE or subquery), and otherwise labels is left unchanged.

Comment on lines +740 to +749
if (provider === 'mysql') {
// MySQL version
const timestampFilterMysql =
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`
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): MySQL conversation listing uses a different "last message" selection semantics than PostgreSQL when applying timestamp filters.

The PostgreSQL query returns the last message within the timestamp window per remoteJid via DISTINCT ON + ORDER BY ... messageTimestamp DESC. In contrast, the MySQL query filters by timestamp only in the outer WHERE, but selects the last message per chat via a correlated subquery on MAX(m2.messageTimestamp) without any timestamp constraint.

This means if the most recent message for a chat is outside the window, but older messages are inside, that chat is excluded in MySQL while included in PostgreSQL (using the last in-range message). To align behavior, the correlated subquery in MySQL should also apply the timestamp filter, or the query should be restructured to emulate DISTINCT ON within the time window.

Comment on lines +764 to +767
DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
Chat.unreadMessages as unreadMessages,
CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive,
Message.id AS lastMessageId,
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): MySQL windowActive is returned as 1/0 instead of boolean, which may diverge from expectations.

In Postgres this column is a boolean expression, but in MySQL it’s 1/0:

CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive

If the TypeScript layer expects a boolean, this mismatch can cause subtle issues (strict equality, serialization, validation). Please update the MySQL query to return TRUE/FALSE or cast to BOOLEAN so both backends expose the same type.

Suggested change
DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
Chat.unreadMessages as unreadMessages,
CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN 1 ELSE 0 END as windowActive,
Message.id AS lastMessageId,
DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) as windowExpires,
Chat.unreadMessages as unreadMessages,
CASE WHEN DATE_ADD(Chat.createdAt, INTERVAL 24 HOUR) > NOW() THEN TRUE ELSE FALSE END as windowActive,
Message.id AS lastMessageId,

Creates new migration to ensure lid column exists even in databases
where it was previously dropped by the Kafka integration migration.

Uses prepared statement to check column existence before adding,
ensuring compatibility with both fresh and existing installations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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