Skip to content

fix: duplicate chats being created for eCurrency - #526

Merged
coodos merged 1 commit into
mainfrom
fix/prevent-2-ecurrency-chats
Dec 8, 2025
Merged

coodos merged 1 commit into
mainfrom
fix/prevent-2-ecurrency-chats

Conversation

@coodos

@coodos coodos commented Dec 8, 2025 •

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

Type of change

  • Breaking (any change that would cause existing functionality to not work as expected)
  • New (a change which implements a new feature)
  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)
  • Docs (changes to the documentation)
  • Chore (refactoring, build scripts or anything else that isn't user-facing)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

New Features

  • Implemented automatic deduplication for eCurrency Chat groups. When creating a new chat, the system now searches for existing chats with matching names and reuses them instead of creating duplicates. Existing chat configurations, participants, and permissions are preserved and synchronized. Regular group chats continue to create new instances as before.

✏️ Tip: You can customize this high-level summary in your review settings.

@coodos
coodos marked this pull request as ready for review December 8, 2025 16:56
@coderabbitai

coderabbitai Bot commented Dec 8, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds eCurrency Chat deduplication logic across chat creation flows. When creating a chat with a name starting with or containing "eCurrency Chat", the system now checks for existing chats with the same name and reuses them, updating mappings and lock lists accordingly, rather than creating duplicates.

Changes

Cohort / File(s) Change Summary
blabsy-w3ds-auth-api Controller
platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts
Adds pre-creation check for eCurrency Chat groups; if a chat with the matching name exists, reuses it (updates lock lists and mappings); otherwise proceeds with existing DM-group detection logic.
pictique-api Controller & Service
platforms/pictique-api/src/controllers/WebhookController.ts
Adds conditional branch in non-DM group chat path; detects eCurrency Chat names and delegates duplicate checking to service layer, falling back to standard chat creation for other group names.
pictique-api Service
platforms/pictique-api/src/services/ChatService.ts
Wraps eCurrency Chat creation in a transaction; checks for existing chat by name first; if found, returns it; if not, resolves participants/creator, validates, and creates new chat. Non-eCurrency chats use existing non-transactional flow.

Sequence Diagram

sequenceDiagram
    participant Controller
    participant ChatService
    participant Database

    Controller->>ChatService: createChat(name, participants, creator)
    
    alt name contains "eCurrency Chat"
        ChatService->>Database: Query chat by name (with relations)
        alt Chat exists
            Database-->>ChatService: Return existing chat
            ChatService-->>Controller: Return reused chat
        else Chat not found
            ChatService->>Database: Resolve participant IDs
            Database-->>ChatService: Validate participants exist
            ChatService->>Database: Create & save new chat (in transaction)
            Database-->>ChatService: Return created chat
            ChatService-->>Controller: Return new chat
        end
    else Non-eCurrency name
        ChatService->>Database: Create new group chat
        Database-->>ChatService: Return created chat
        ChatService-->>Controller: Return chat
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review pictique-api/src/controllers/WebhookController.ts for conditional logic correctness and proper delegation to service layer
  • Verify transaction handling in ChatService.ts for concurrent access edge cases
  • Confirm participant/admin resolution and validation in both eCurrency and non-eCurrency paths
  • Test deduplication logic: ensure name matching is precise and existing chat reuse updates all required fields (lock lists, mappings, admins)

Possibly related PRs

  • Fix/dreamsync bugs #371: Implements similar chat deduplication and name-based reuse logic for eCurrency chats across platforms, addressing the same duplicate-prevention concern.

Suggested reviewers

  • sosweetham

Poem

🐰 Duplicate chats begone, we say with glee,
eCurrency names now checked with certainty,
Reuse the old, or craft something new,
Transaction-safe, the logic is true! 🎭✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description contains only the template structure with no actual content filled in. All required sections (Issue Number, Type of change selection, How the change has been tested) are empty or unchecked. Fill in all required sections: provide the issue number, select the type of change (appears to be 'Fix'), describe testing methodology, and check the relevant checklist items that apply.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing duplicate chats being created for eCurrency. It is concise, specific, and directly related to the primary objective of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/prevent-2-ecurrency-chats

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.

@coodos
coodos merged commit 590e742 into main Dec 8, 2025
3 of 4 checks passed
@coodos
coodos deleted the fix/prevent-2-ecurrency-chats branch December 8, 2025 16:58

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 0

🧹 Nitpick comments (5)
platforms/pictique-api/src/services/ChatService.ts (1)

66-66: Redundant condition: startsWith is covered by includes.

The check name.startsWith("eCurrency Chat") || name.includes("eCurrency Chat") is redundant since includes already covers all cases where startsWith would return true.

-        if (name && (name.startsWith("eCurrency Chat") || name.includes("eCurrency Chat"))) {
+        if (name?.includes("eCurrency Chat")) {
platforms/pictique-api/src/controllers/WebhookController.ts (2)

300-346: Significant code duplication across branches.

The three branches (existing eCurrency Chat, new eCurrency Chat, regular group chat) share nearly identical post-processing logic (lines 312-318, 325-331, 339-345). This increases maintenance burden.

Consider extracting the common logic:

                    } else {
-                        // Group chat - check for eCurrency Chat duplicates by name
-                        const chatName = local.data.name as string;
-                        if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) {
-                            // Check if chat with this name already exists
-                            const existingChat = await this.chatService.chatRepository.findOne({
-                                where: { name: chatName },
-                                relations: ["participants", "admins"]
-                            });
-                            
-                            if (existingChat) {
-                                console.log(`⚠️ eCurrency Chat with name "${chatName}" already exists, using existing: ${existingChat.id}`);
-                                chat = existingChat;
-                                chat.admins = admins;
-                                await this.chatService.chatRepository.save(chat);
-                                this.adapter.addToLockedIds(chat.id);
-                                await this.adapter.mappingDb.storeMapping({
-                                    localId: chat.id,
-                                    globalId: req.body.id,
-                                });
-                            } else {
-                                // Create new chat
-                                chat = await this.chatService.createChat(
-                                    chatName,
-                                    participantIds
-                                );
-                                chat.admins = admins;
-                                await this.chatService.chatRepository.save(chat);
-                                this.adapter.addToLockedIds(chat.id);
-                                await this.adapter.mappingDb.storeMapping({
-                                    localId: chat.id,
-                                    globalId: req.body.id,
-                                });
-                            }
-                        } else {
-                            // Regular group chat - always create new
-                            chat = await this.chatService.createChat(
-                                chatName,
-                                participantIds
-                            );
-                            chat.admins = admins;
-                            await this.chatService.chatRepository.save(chat);
-                            this.adapter.addToLockedIds(chat.id);
-                            await this.adapter.mappingDb.storeMapping({
-                                localId: chat.id,
-                                globalId: req.body.id,
-                            });
-                        }
+                        // Group chat - createChat handles eCurrency deduplication internally
+                        const chatName = local.data.name as string;
+                        chat = await this.chatService.createChat(
+                            chatName,
+                            participantIds
+                        );
+                        chat.admins = admins;
+                        await this.chatService.chatRepository.save(chat);
+                        this.adapter.addToLockedIds(chat.id);
+                        await this.adapter.mappingDb.storeMapping({
+                            localId: chat.id,
+                            globalId: req.body.id,
+                        });
                    }

Since ChatService.createChat already handles eCurrency Chat deduplication transactionally (lines 64-102 in ChatService.ts), the redundant check here can be removed, simplifying this code significantly.


302-307: Same redundant condition and direct repository access.

  1. The condition chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat") is redundant (same as in ChatService).

  2. Accessing this.chatService.chatRepository.findOne directly breaks encapsulation. The ChatService already exposes a findById method; consider adding a findByName method if needed.

platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts (2)

167-167: Same redundant condition.

chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat") is redundant—includes covers all startsWith cases.

-            if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) {
+            if (chatName?.includes("eCurrency Chat")) {

168-183: Non-atomic check-then-skip could still allow race conditions.

The query and conditional skip are not atomic. Under concurrent webhook calls, two requests could both pass the existingChatsSnapshot.empty check before either stores the mapping.

For Firestore, consider using a transaction or setting the document with a deterministic ID (e.g., derived from the chat name) to leverage Firestore's idempotent set operation.

Additionally, if multiple documents somehow exist with the same name (from a prior race), only docs[0] is used silently. Consider logging a warning if existingChatsSnapshot.size > 1.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e9617e and 0720de8.

📒 Files selected for processing (3)
  • platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts (1 hunks)
  • platforms/pictique-api/src/controllers/WebhookController.ts (1 hunks)
  • platforms/pictique-api/src/services/ChatService.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
platforms/pictique-api/src/services/ChatService.ts (1)
platforms/pictique-api/src/database/data-source.ts (1)
  • AppDataSource (15-29)
platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts (3)
platforms/pictique-api/src/web3adapter/watchers/subscriber.ts (1)
  • adapter (17-22)
platforms/evoting-api/src/web3adapter/watchers/subscriber.ts (1)
  • adapter (15-20)
platforms/group-charter-manager-api/src/web3adapter/watchers/subscriber.ts (1)
  • adapter (15-20)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (2)
platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts (1)

164-184: Approve the deduplication approach with suggestions above.

The early-exit pattern for existing eCurrency Chats is clean and aligns with the pictique-api changes. The implementation correctly reuses the existing chat and stores the global-to-local mapping.

platforms/pictique-api/src/services/ChatService.ts (1)

67-102: Consider database-level constraints for chat name uniqueness.

The transactional approach in the code is a solid improvement, but under PostgreSQL's default READ COMMITTED isolation level, a race condition could occur where two concurrent transactions both pass the findOne check before either commits. Add a unique constraint on the name column, or use SERIALIZABLE isolation for this operation to ensure robust protection against duplicate chat creation.

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