Skip to content

fix(api): delete message_reactions before purging a channel account - #385

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-api-delete-message-reactions-before-purging-a-248d53
Open

fix(api): delete message_reactions before purging a channel account#385
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-api-delete-message-reactions-before-purging-a-248d53

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Closes #380

Problem

purgeArchivedChannelAccount permanently deletes an archived channel-neutral account (e.g. Telegram) and all of its conversations, messages, routing rows, and bridged contacts inside one PostgreSQL transaction. The channel-spine schema adds message_reactions.channel_account_id → channel_accounts(id) ON DELETE RESTRICT, but the purge never deleted message_reactions rows. Deleting the messages did not help — the base message_reactions table has no FK on message_id, so reactions are not cascade-removed.

As a result, DELETE FROM channel_accounts tripped the message_reactions_account_fk RESTRICT and the entire transaction rolled back. Any archived account that had received or sent at least one reaction (written only when a workspace enables the neutral spine, write_authority = 'neutral' + provider on) could never be purged — the failure was deterministic and the blocking rows never cleared.

Fix

Delete the account's message_reactions rows inside the purge transaction, before DELETE FROM channel_accounts (apps/api/src/services/channel-account-purge.service.ts):

await trx.deleteFrom("message_reactions")
  .where("channel_account_id", "=", accountId).execute();

The delete is scoped by channel_account_id (not message_id) on purpose: it matches the RESTRICT FK column and also reaps any reactions whose message row was already deleted elsewhere — a narrower message_id-in-(messageIds) scope (as the WhatsApp linked-device purge uses) would miss those orphans. This mirrors the existing precedent in whatsapp/connection.ts, which already cleans message_reactions explicitly because of the absent message_id FK. No schema/migration change is needed; the constraint is correct, the application just had to satisfy it.

Testing

  • Integration tests committed (channel-account-purge.integration.test.ts, run with RUN_DB_INTEGRATION=1 against a migrated PostgreSQL):
    • Seeded an archived Telegram account with a message_reactions row (channel_account_id set), then purged — the account, conversation, message, and reaction are all gone and conversation_cases is empty.
    • Seeded a reaction whose message_id has no matching messages row, then purged — the orphaned reaction is reaped, pinning the channel_account_id-scope decision so a future narrowing to message_id scope can't silently reintroduce the RESTRICT failure.
  • Regression-catch check (not versioned): temporarily reverted the new delete and re-ran the integration tests — they failed with the exact message_reactions_account_fk RESTRICT violation (code 23001, constraint "message_reactions_account_fk"), then passed again with the fix restored.
  • HTTP route and guards (not versioned, run against the live app via app.request): verified POST /api/channel-accounts/:id/purge returns 200 with the purge payload for an archived account with a reaction, 403 for a member, and 409 for a non-archived account; and that a non-archived account rejects with ChannelAccountNotArchivedError and leaves all seeded data intact, missing accounts reject with NotFoundError, and legacy WhatsApp-bridged accounts reject with the linked-device-flow error. Also confirmed reactions on other accounts and legacy channel_account_id IS NULL rows are preserved (not over-deleted).
  • Unaffected surfaces: channel-spine schema integration, the inbound/outbound reaction-writer paths, and the legacy WhatsApp linked-device purge (unit + integration) all pass.
  • Full integration sweep: 110 of 114 TypeScript integration files pass. The 5 failures are environmental and outside this change's blast radius — media/avatar/bulk/scheduled-message tests need a running object store (MinIO), and the background-dispatch test needs the nats_outbox relation; none reference message_reactions, channel_accounts, or the purge service.
  • Routine checks: typecheck, lint, format, and build all pass; the API unit suite is green with no regressions.

Operational / security impact

None. This is a data-deletion correctness fix: the purge now removes the reactions it was always supposed to remove. No new data is exposed, no credentials or external calls are involved, and no behavior changes for accounts without neutral-spine reactions.


Automatic Fixes PRs can be configured here.

@detail-app
detail-app Bot requested a review from setkyar September 11, 2026 21:57
@setkyar
setkyar force-pushed the detail/bug-fix/fix-api-delete-message-reactions-before-purging-a-248d53 branch from 160173d to 7f69b37 Compare September 12, 2026 14:04
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.

[Detail Bug] Channel account purge fails when reactions exist (FK RESTRICT blocks deletion)

1 participant