Skip to content

Conversation

xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Sep 25, 2025

Submit a pull request

Fixes: FLU-225
LLC PR: #2394

Description of the pull request

This PR introduces two new fields to the persistence layer:

  • deletedForMe (boolean) to MessageEntity and PinnedMessageEntity: Indicates if a message was deleted only for the current user.
  • deletedMessages (list of strings) to MemberEntity: Stores a list of message IDs that a member has deleted only for themselves.

These changes allow for more granular control over message deletion states within the local database.

The database schema version has been incremented to 1025 to reflect these changes.
Mapper and test files have been updated accordingly.

Summary by CodeRabbit

  • New Features

    • Per-user deletion support: messages and pinned messages can be marked deleted for a specific user; members track their own deleted message IDs.
  • Documentation

    • Changelog updated with an "Upcoming Beta" note describing per-user deletion support.
  • Chores

    • Local database schema bumped to accommodate new per-user deletion fields.
  • Tests

    • Mapping tests updated to cover deleted-for-me and member deleted-messages propagation.

This commit introduces the ability to delete a message only for the current user.

The following changes are included:
- Added `MessageDeleteScope` to represent the scope of deletion for a message.
- Added `deleteMessageForMe` method to `StreamChatClient` and `Channel` to delete a message only for the current user.
- Updated `Message` model to include `deletedOnlyForMe` field.
- Updated `Event` model to include `deletedForMe` field.
- Updated `Member` model to include `deletedMessages` field.
- Updated `MessageState` to include `deletingForMe`, `deletedForMe`, and `deletingForMeFailed` states.
- Updated `MessageApi` to include `delete_for_me` parameter in `deleteMessage` method.
- Updated sample app to include "Delete Message for Me" option in message actions.
This commit introduces a new `sentenceCase` extension for strings and replaces all usages of the deprecated `capitalize` extension with it.

The `sentenceCase` extension converts a string to sentence case, where the first letter is capitalized and the rest of the string is in lowercase. This provides more accurate capitalization for various UI elements.

The `capitalize` extension has been deprecated and will be removed in a future release.
This commit renames the `DeleteForMe` class and its usages to `DeleteMessageForMe` for clarity.
The documentation for MessageDeleteScope was updated to reflect the new names for the enum values. `forMe` was changed to `deleteForMe` and `forAll` was changed to `deleteForAll`.
This commit introduces a new message action that allows users to delete a message that failed to send.

The new action is added to the `MessageActionsBuilder` and is only displayed when the message state is `isSendingFailed`.
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

Adds per-user deletion support: new Messages.deletedForMe (bool) and Members.deletedMessages (List) columns, schema version bumped to 1000 + 25, mappers updated to propagate these fields, generated drift code refreshed, and tests updated. CHANGELOG notes upcoming beta support.

Changes

Cohort / File(s) Summary
Schema bump
packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart
Increment schemaVersion from 1000 + 24 to 1000 + 25.
DB entities: new columns
packages/stream_chat_persistence/lib/src/entity/messages.dart, packages/stream_chat_persistence/lib/src/entity/members.dart
Add deletedForMe BoolColumn to Messages (nullable) and deletedMessages TextColumn with ListConverter<String>() to Members.
Generated drift code
packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart
Regenerate to include Messages.deletedForMe, PinnedMessages.deletedForMe, Members.deletedMessages; update entities, companions, converters, mappings, JSON serialization, equality/hashCode, and toString.
Mappers
packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart, .../pinned_message_mapper.dart, .../member_mapper.dart
Propagate new fields both directions: MessageEntity.deletedForMe ↔ Message.deletedForMe, PinnedMessageEntity.deletedForMe ↔ Message.deletedForMe, MemberEntity.deletedMessages ↔ Member.deletedMessages. Also use state.toJson() for state serialization in mappings.
Tests
packages/stream_chat_persistence/test/src/mapper/*.dart
Update/add tests to assert round-trip mapping of deletedForMe and deletedMessages for messages, pinned messages, and members.
Changelog
packages/stream_chat_persistence/CHANGELOG.md
Add "Upcoming Beta" note mentioning support for Messages.deletedForMe, PinnedMessages.deletedForMe, and Members.deletedMessages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant Model as Domain Model
  participant Mapper
  participant DB as Drift DB

  rect rgba(227,242,253,0.6)
    note right of App: User marks message "deleted for me"
    App->>Model: set Message.deletedForMe = true
    Model->>Mapper: MessageX.toEntity()
    Mapper->>DB: upsert MessageEntity{ deletedForMe: true }
  end

  rect rgba(232,245,233,0.6)
    note right of App: Member deletes messages locally
    App->>Model: update Member.deletedMessages = [...]
    Model->>Mapper: MemberX.toEntity()
    Mapper->>DB: upsert MemberEntity{ deletedMessages: [...] }
  end

  rect rgba(255,243,224,0.6)
    note right of DB: Read path
    DB-->>Mapper: fetch entities (Messages/Members/PinnedMessages)
    Mapper-->>Model: toMessage()/toMember() includes deletedForMe / deletedMessages
    Model-->>App: domain objects with new fields
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • renefloor
  • Brazol

Poem

I nibble through tables with whiskers bright,
Mark a message hidden from my sight.
Members stash lists in cozy nooks,
Pinned bits remember tiny hooks.
Schema hopped up — a jubilant bite! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The patch includes modifications to how message state is serialized in the message and pinned message mappers—switching from jsonEncode(state) to state.toJson()—which falls outside the stated goal of adding per-user deletion fields and is unrelated to FLU-225. Isolate or remove the state serialization changes in a separate pull request so this one remains focused on the deletedForMe and deletedMessages feature.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The pull request fully implements the coding requirements implied by FLU-225 by adding the deletedForMe flag to MessageEntity and PinnedMessageEntity, introducing the deletedMessages list to MemberEntity, bumping the schema version, and updating mappers and tests to exercise these features.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main change by specifying the addition of the deletedForMe and deletedMessages fields to the persistence layer, aligning directly with the PR’s objectives. It is clear, concise, and uses a conventional scope prefix to guide reviewers.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/delete-for-me-persistence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@xsahil03x
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

github-actions bot commented Sep 25, 2025

⚠️ Database Entity Files Modified

The following database entity files have been modified in this PR:

packages/stream_chat_persistence/lib/src/entity/members.dart
packages/stream_chat_persistence/lib/src/entity/messages.dart

📝 Remember to:

  1. Update database version in db/drift_chat_database.dart.
  2. Update entity schema tests if necessary.

Note: This comment is automatically generated by the CI workflow.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart (3)

1371-1405: Guard deletedForMe against missing JSON fields.

serializer.fromJson<bool>(json['deletedForMe']) will throw when the key is absent or null in persisted JSON (older caches / payloads). We need to default to false to remain backwards-compatible.

-      deletedForMe: serializer.fromJson<bool>(json['deletedForMe']),
+      deletedForMe: serializer.fromJson<bool?>(json['deletedForMe']) ?? false,

Please adjust the generator/source so the generated code handles the missing field gracefully.


4006-4042: Same fix needed for pinned message deserialization.

Persisted pinned-message JSON can lack deletedForMe. Deserialising with a non-null cast will blow up; default it to false.

-      deletedForMe: serializer.fromJson<bool>(json['deletedForMe']),
+      deletedForMe: serializer.fromJson<bool?>(json['deletedForMe']) ?? false,

7790-7808: Default deletedMessages to an empty list when missing.

Older JSON blobs won’t carry deletedMessages. Calling serializer.fromJson<List<String>>(null) will throw. Default it to const <String>[] to keep things robust.

-      deletedMessages:
-          serializer.fromJson<List<String>>(json['deletedMessages']),
+      deletedMessages:
+          serializer.fromJson<List<String>?>(json['deletedMessages']) ??
+              const <String>[],
🧹 Nitpick comments (6)
packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart (1)

21-23: Ensure deletedMessages is non-null when mapping to MemberEntity

The DB column is non-nullable. Guard against nulls from older payloads or partially constructed Member objects.

Apply this diff:

-        deletedMessages: deletedMessages,
+        deletedMessages: deletedMessages ?? const [],

Also confirm that Member.deletedMessages in stream_chat is non-nullable with a default empty list. If nullable in any codepath, consider a similar null-coalesce on the toMember side as well.

Also applies to: 43-45

packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart (1)

60-60: Schema bump will trigger destructive migration; prefer additive migration for new columns

The current onUpgrade deletes and recreates all tables, causing data loss on upgrade (even for additive changes). For these additive fields, consider targeted migrations.

Example approach:

@override
MigrationStrategy get migration => MigrationStrategy(
  beforeOpen: (_) async => await customStatement('PRAGMA foreign_keys = ON'),
  onUpgrade: (migrator, from, to) async {
    // Example: Additive migrations when upgrading to 1025
    if (from < 1025) {
      await migrator.addColumn(messages, messages.deletedForMe);
      await migrator.addColumn(pinnedMessages, pinnedMessages.deletedForMe);
      await migrator.addColumn(members, members.deletedMessages);
    }
  },
);

If destructive migration is intentional during beta, please confirm and document the data-loss expectation in release notes.

packages/stream_chat_persistence/lib/src/entity/members.dart (1)

3-3: Default deletedMessages to [] and minor doc tweak

  • Without a default, inserts must always provide a value; defaulting to [] improves resilience.
  • Minor wording tweak for inclusivity.

Apply this diff:

-/// List of message ids deleted by the member only for himself.
+/// List of message IDs deleted by the member only for themselves.
 ///
-/// These messages are now marked deleted for this member, but are still
-/// kept as regular to other channel members.
-TextColumn get deletedMessages => text().map(ListConverter<String>())();
+/// These messages are marked deleted for this member, but remain visible
+/// to other channel members.
+TextColumn get deletedMessages =>
+    text().withDefault(const Constant('[]')).map(ListConverter<String>())();

Note: If Drift requires withDefault before/after map in a specific order, adjust accordingly (some versions require calling withDefault before map()).

Also applies to: 54-59

packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart (2)

34-34: Null-safety for deletedOnlyForMe mapping

If deletedForMe can be null (older rows/migration gaps), default to false to avoid propagating nulls into Message.deletedOnlyForMe.

-        deletedOnlyForMe: deletedForMe,
+        deletedOnlyForMe: deletedForMe ?? false,

89-89: Null-safety for deletedForMe persistence

Mirror the read-path safeguard; default to false when persisting if deletedOnlyForMe can be null. Also ensure the Drift column has a non-null default of false.

-        deletedForMe: deletedOnlyForMe,
+        deletedForMe: deletedOnlyForMe ?? false,
packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart (1)

82-84: LGTM: assertion validates round-trip for deletedMessages

Consider adding an extra test for empty lists (and, if applicable, null) to harden edge cases.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 80e7964 and d20f7e2.

📒 Files selected for processing (11)
  • packages/stream_chat_persistence/CHANGELOG.md (1 hunks)
  • packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart (1 hunks)
  • packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart (99 hunks)
  • packages/stream_chat_persistence/lib/src/entity/members.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/entity/messages.dart (1 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart (4 hunks)
  • packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart (4 hunks)
  • packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: test
🔇 Additional comments (8)
packages/stream_chat_persistence/lib/src/entity/messages.dart (1)

102-104: LGTM: added per-user deletion flag with safe default

The new column and default(false) look correct and migration-friendly.

packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart (1)

83-85: LGTM: tests cover deletedForMe ↔ deletedOnlyForMe mapping both ways

Good coverage for entity→model and model→entity propagation.

Also applies to: 134-135

packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart (1)

34-36: LGTM: consistent mapping between deletedForMe and deletedOnlyForMe

Symmetric propagation in both directions looks correct.

Also applies to: 90-92

packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart (1)

83-84: LGTM: tests validate deletedForMe ↔ deletedOnlyForMe mapping

Assertions on both directions are accurate and consistent with the mappers.

Also applies to: 134-135, 223-224, 262-263

packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart (3)

27-29: LGTM: entity → model mapping covers deletedMessages

Good coverage for the new field on the entity-to-model path.


44-46: LGTM: assertion validates deletedMessages propagation

Deep equality via expect(actual, expected) works for lists here.


64-66: LGTM: model → entity mapping covers deletedMessages

Const list usage is fine and verifies immutability expectations.

packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart (1)

82-82: Verify schema v1025 migration and backward‐compatibility for message state
No migration rewriting messages.state for v1025 was found; confirm or add a Drift migration to normalize existing rows to the new JSON shape and make MessageState.fromJson resilient to both legacy string enums and JSON map inputs.

@xsahil03x xsahil03x force-pushed the feat/delete-for-me-persistence branch from d20f7e2 to c41a842 Compare September 25, 2025 08:05
@xsahil03x
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

codecov bot commented Sep 25, 2025

Codecov Report

❌ Patch coverage is 50.00000% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.05%. Comparing base (be9ebfc) to head (0c0ee7e).

Files with missing lines Patch % Lines
...lib/src/message_widget/message_widget_content.dart 0.00% 9 Missing ⚠️
...flutter/lib/src/message_widget/message_widget.dart 50.00% 1 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                   @@
##           feat/delete-for-me    #2395      +/-   ##
======================================================
- Coverage               65.05%   65.05%   -0.01%     
======================================================
  Files                     421      421              
  Lines                   26054    26066      +12     
======================================================
+ Hits                    16950    16957       +7     
- Misses                   9104     9109       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xsahil03x xsahil03x changed the title feat(persistence): Add deletedForMe and deletedMessages fields feat(persistence): add deletedForMe and deletedMessages fields Sep 25, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart (1)

7529-7534: Modeling note: consider normalizing per‑user deletions instead of a growing JSON list.

Storing all deleted message IDs in one TEXT column can bloat rows and complicate targeted queries. A join table (user_id, channel_cid, message_id) with indexes scales better if this list grows.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d20f7e2 and 8e2fdf1.

📒 Files selected for processing (11)
  • packages/stream_chat_persistence/CHANGELOG.md (1 hunks)
  • packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart (1 hunks)
  • packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart (99 hunks)
  • packages/stream_chat_persistence/lib/src/entity/members.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/entity/messages.dart (1 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart (2 hunks)
  • packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart (4 hunks)
  • packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart (4 hunks)
  • packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart
  • packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart
  • packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart
  • packages/stream_chat_persistence/CHANGELOG.md
  • packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart
  • packages/stream_chat_persistence/lib/src/entity/messages.dart
  • packages/stream_chat_persistence/lib/src/entity/members.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: analyze
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: build (ios)
🔇 Additional comments (4)
packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart (1)

21-44: Good symmetry on deletedMessages mapping.
Both directions now persist the new field, so the entity and model will stay in sync without extra work.

packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart (1)

34-34: Great job propagating deletedForMe.

Both directions now keep the flag in sync, so pinned-message hydration and persistence remain consistent with the new per-user deletion semantics.

Also applies to: 90-90

packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart (1)

83-84: Tests cover the new field thoroughly.

I appreciate the explicit round-trip assertions for deletedForMe; they’ll catch regressions during future mapper tweaks.

Also applies to: 223-224, 134-135, 262-263

packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart (1)

7529-7534: Members.deletedMessages column is safe as-is
DB upgrade strategy drops and recreates all tables on schemaVersion bump, and every insert via the generated Companion.insert requires a non-null deletedMessages list (the mapper always supplies a default List), so no backfill or default is needed.

This commit introduces two new fields to the persistence layer:

- `deletedForMe` (boolean) to `MessageEntity` and `PinnedMessageEntity`: Indicates if a message was deleted only for the current user.
- `deletedMessages` (list of strings) to `MemberEntity`: Stores a list of message IDs that a member has deleted only for themselves.

These changes allow for more granular control over message deletion states within the local database.

The database schema version has been incremented to `1025` to reflect these changes.
Mapper and test files have been updated accordingly.
The `deletedOnlyForMe` field in `MessageEntity` has been renamed to `deletedForMe` to match the API.
The `deletedForMe` field in `MessageEntity` is now nullable.
@xsahil03x xsahil03x force-pushed the feat/delete-for-me-persistence branch from 3882e65 to c3d39ce Compare September 25, 2025 08:44
@xsahil03x xsahil03x force-pushed the feat/delete-for-me-persistence branch from 848de27 to c3d39ce Compare September 25, 2025 17:28
Base automatically changed from feat/delete-for-me to v10.0.0 September 26, 2025 14:26
@xsahil03x xsahil03x merged commit 5a02491 into v10.0.0 Sep 26, 2025
17 checks passed
@xsahil03x xsahil03x deleted the feat/delete-for-me-persistence branch September 26, 2025 14:27
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.

2 participants