Skip to content

feat(backend): edit/repost as new ciphertext version#277

Open
Olorunfemi20 wants to merge 1 commit into
codebestia:mainfrom
Olorunfemi20:feat/message-edit-ciphertext
Open

feat(backend): edit/repost as new ciphertext version#277
Olorunfemi20 wants to merge 1 commit into
codebestia:mainfrom
Olorunfemi20:feat/message-edit-ciphertext

Conversation

@Olorunfemi20

Copy link
Copy Markdown
Contributor

Summary

Implements message editing as an append-only operation. An edit never mutates the original row's ciphertext in place; instead it creates a brand-new message carrying fresh ciphertext and per-device envelopes, linked back to the message it replaces through a new nullable self-referential editsMessageId FK on messages. The gateway broadcasts message_edited { originalMessageId, newMessageId } so clients can resolve a thread to its newest version and render an "edited" marker.

closes #190

Changes

  • Schema (db/schema.ts): add editsMessageId uuid to messages, a self-referential FK to messages.id with ON DELETE SET NULL (deleting an original does not cascade away its edits). Added the editsMessage / edits self-relation (shared relationName) so Drizzle can resolve the self-join. The inferred Message type now carries the field automatically.
  • Migration 0009_message_edits.sql (+ journal entry): ADD COLUMN edits_message_id and the FK constraint. Standard additive DDL that applies cleanly on top of the existing messages table.
  • Socket gateway (socket/messaging.ts): new edit_message handler that
    • requires originalMessageId and the client-generated new messageId,
    • rejects empty content,
    • loads the original and restricts editing to the original sender (senderId === caller),
    • links the new message to the root original (original.editsMessageId ?? original.id) so a chain of edits always collapses to one logical message,
    • is idempotent: a retried edit with an already-seen messageId re-acks without inserting again,
    • persists the new message + fresh message_envelopes, then broadcasts new_message (so recipient devices receive the new ciphertext to decrypt) and message_edited.

The handler mirrors the existing send_message flow (idempotency check, envelope fan-out, message_ack, cache invalidation) to stay consistent with the established patterns.

Acceptance criteria

  • Edits create a linked new message + fresh envelopes: a new messages row is inserted with editsMessageId set and new message_envelopes rows are written; new_message is broadcast.
  • Clients resolve to the newest version per original: edits link back to the root original id and message_edited { originalMessageId, newMessageId } is broadcast for clients to supersede the previous version.
  • Edit authorship restricted to the original sender: non-senders receive an error and no write occurs.

Testing

  • src/__tests__/messageEdit.test.ts covers: missing ids, empty content, non-sender rejection, linked-new-message creation with new_message + message_edited broadcast, edit-of-an-edit collapsing to the root, and idempotent replay.
  • Full backend suite passes (135 tests), tsc --noEmit clean, ESLint clean (no new warnings), Prettier clean.

@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@Olorunfemi20 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Edit/repost as new ciphertext (no in-place plaintext edit)

1 participant