Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Jan 30, 2026

Description of change

User's get notification if wishlist match is not found at dreamsync

Issue Number

closes #752

Type of change

  • New (a change which implements a new feature)

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
    • Added notifications for users with active wishlists who don't receive a match during the matching process. Notifications are automatically sent via chat after the matching run completes.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

The changes add a no-match notification system that identifies users with active non-private wishlists who didn't receive matches during a matching run and sends them system messages encouraging wishlist expansion.

Changes

Cohort / File(s) Summary
No-Match Orchestration
platforms/dreamsync-api/src/services/AIMatchingService.ts
Adds two private methods: getAllWishlistUserIdsForNoMatch() queries active wishlist users and sendNoMatchNotifications() compares against matched users and triggers notifications; integrated into findMatches() after processUnmessagedMatches.
No-Match Notification
platforms/dreamsync-api/src/services/MatchNotificationService.ts
Adds sendNoMatchNotification(userId) method that resolves user, finds/creates mutual chat (with 15-second delay for new chats), and sends a system message indicating no matches were found; includes error handling and logging. Note: Method appears duplicated in diff.

Sequence Diagram

sequenceDiagram
    participant AIMatch as AIMatchingService
    participant DB as Database
    participant NotifSvc as MatchNotificationService
    participant Chat as Chat System
    participant Message as Message Store

    AIMatch->>AIMatch: processUnmessagedMatches()
    AIMatch->>DB: getAllWishlistUserIdsForNoMatch()
    DB-->>AIMatch: [wishlistUserIds]
    AIMatch->>DB: Query matched users
    DB-->>AIMatch: [matchedUserIds]
    AIMatch->>AIMatch: Compute no-match candidates
    
    loop For each no-match user
        AIMatch->>NotifSvc: sendNoMatchNotification(userId)
        NotifSvc->>DB: Resolve user details
        DB-->>NotifSvc: User info
        NotifSvc->>Chat: Find/create mutual chat
        Chat-->>NotifSvc: Chat (possibly new)
        alt New chat created
            NotifSvc->>NotifSvc: Wait 15 seconds
        end
        NotifSvc->>Message: Create system message
        Message-->>NotifSvc: Saved
        NotifSvc-->>AIMatch: Notification sent
    end
    AIMatch->>AIMatch: Log totals
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 When wishlists bloom but matches don't appear,
A fuzzy message brings them gentle cheer!
"Add more details," DreamSync softly pleads,
While we hop and notify all matching needs.
No silence now—just kindness in their feed! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The changes introduce a critical code quality issue: MatchNotificationService contains an identical duplicate implementation of sendNoMatchNotification method, suggesting a merge artifact rather than intentional design. Remove the duplicate sendNoMatchNotification method implementation from MatchNotificationService to eliminate redundant code.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: message users when no match' clearly and concisely describes the main change: implementing a feature to send notifications to users when DreamSync finds no matches.
Description check ✅ Passed The PR description includes all required sections with substantial content: change description, issue number, type of change, and a completed checklist. However, the 'How the change has been tested' section is missing.
Linked Issues check ✅ Passed The implementation adds no-match notification functionality to AIMatchingService and MatchNotificationService that detects users with wishlists who received no matches and sends them notifications, addressing issue #752's core requirement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/dreamsync-empty-notif-messages

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.

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

🤖 Fix all issues with AI agents
In `@platforms/dreamsync-api/src/services/AIMatchingService.ts`:
- Around line 141-156: The current getAllWishlistUserIdsForNoMatch collects all
active wishlist owners (including empty wishlists) and ignores the run's
200‑wishlist processing cap; change the query in getAllWishlistUserIdsForNoMatch
(using wishlistRepository.createQueryBuilder("wishlist")) to only include
wishlists that were actually analyzed: add a predicate to exclude empty
wishlists (e.g., where wishlist.items IS NOT NULL or wishlist.itemCount > 0 or
the appropriate non-empty column for your schema) and apply the same
limit/offset or 200‑item cap used elsewhere in this run so the returned userIds
exactly match the processed, non‑empty wishlists. Ensure the distinct userId
select remains and keep the processLogger/console message updated if desired.

In `@platforms/dreamsync-api/src/services/MatchNotificationService.ts`:
- Around line 722-732: In MatchNotificationService update the no-match message
string assigned to messageContent (using displayName) to a clearer,
grammatically correct user-facing copy: replace the current body with a concise
message such as "Dear {displayName},\n\nWe couldn’t find any conclusive matches
for your wishlist this time. Try adding more details or topics to increase your
chances of finding a match.\n\nBest wishes,\nDreamSync" (preserve the
$$system-message$$ wrapper if required), ensuring proper capitalization and
removing repeated phrasing.
🧹 Nitpick comments (1)
platforms/dreamsync-api/src/services/AIMatchingService.ts (1)

161-188: Consider batching no‑match sends to avoid very long runs.
The loop is fully sequential; with the 15‑second delay in sendNoMatchNotification, large batches can make a run take hours. A small concurrency cap keeps throughput reasonable. Please confirm the cap is compatible with any messaging/DB rate limits.

♻️ Example batching with a small concurrency cap
-        for (let i = 0; i < noMatchUserIds.length; i++) {
-            const userId = noMatchUserIds[i];
-            try {
-                console.log(`📨 [no-match] (${i + 1}/${noMatchUserIds.length}) Sending no-match notification to user: ${userId}`);
-                await this.notificationService.sendNoMatchNotification(userId);
-                sentCount++;
-                console.log(`✅ [no-match] Sent to user ${userId} (${sentCount}/${noMatchUserIds.length})`);
-            } catch (error) {
-                errorCount++;
-                console.error(`❌ [no-match] Error sending no-match notification to user ${userId}:`, error);
-            }
-        }
+        const batchSize = 10;
+        for (let i = 0; i < noMatchUserIds.length; i += batchSize) {
+            const batch = noMatchUserIds.slice(i, i + batchSize);
+            await Promise.all(batch.map(async (userId, idx) => {
+                const ordinal = i + idx + 1;
+                try {
+                    console.log(`📨 [no-match] (${ordinal}/${noMatchUserIds.length}) Sending no-match notification to user: ${userId}`);
+                    await this.notificationService.sendNoMatchNotification(userId);
+                    sentCount++;
+                    console.log(`✅ [no-match] Sent to user ${userId} (${sentCount}/${noMatchUserIds.length})`);
+                } catch (error) {
+                    errorCount++;
+                    console.error(`❌ [no-match] Error sending no-match notification to user ${userId}:`, error);
+                }
+            }));
+        }

Comment on lines +141 to +156
/**
* Get all user IDs that have at least one active wishlist (non-null record), regardless of content/empty.
* Used for no-match notifications so we include users whose wishlists may be considered "empty".
*/
private async getAllWishlistUserIdsForNoMatch(): Promise<string[]> {
const rows = await this.wishlistRepository
.createQueryBuilder("wishlist")
.select("DISTINCT wishlist.userId")
.leftJoin("wishlist.user", "user")
.where("wishlist.isActive = :isActive", { isActive: true })
.andWhere("user.isPrivate = :isPrivate", { isPrivate: false })
.getRawMany<Record<string, string>>();
const userIds = rows.map(r => Object.values(r)[0]).filter(Boolean) as string[];
console.log(`📋 [no-match] Found ${userIds.length} distinct users with at least one active wishlist (including empty wishlists)`);
return userIds;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Limit no‑match candidates to wishlists actually analyzed (and non‑empty).
The current query scans all active wishlists (including empty) and ignores the 200‑wishlist cap used in this run, so users not processed could receive a false “no match” message. This also conflicts with the “non‑empty wishlist” requirement.

✅ Suggested fix to align eligibility with the processed, non‑empty wishlists
-            const allWishlistUserIds = await this.getAllWishlistUserIdsForNoMatch();
-            await this.sendNoMatchNotifications(allWishlistUserIds, matchResults);
+            const allWishlistUserIds = this.getAllWishlistUserIdsForNoMatch(wishlistData);
+            await this.sendNoMatchNotifications(allWishlistUserIds, matchResults);
-    /**
-     * Get all user IDs that have at least one active wishlist (non-null record), regardless of content/empty.
-     * Used for no-match notifications so we include users whose wishlists may be considered "empty".
-     */
-    private async getAllWishlistUserIdsForNoMatch(): Promise<string[]> {
-        const rows = await this.wishlistRepository
-            .createQueryBuilder("wishlist")
-            .select("DISTINCT wishlist.userId")
-            .leftJoin("wishlist.user", "user")
-            .where("wishlist.isActive = :isActive", { isActive: true })
-            .andWhere("user.isPrivate = :isPrivate", { isPrivate: false })
-            .getRawMany<Record<string, string>>();
-        const userIds = rows.map(r => Object.values(r)[0]).filter(Boolean) as string[];
-        console.log(`📋 [no-match] Found ${userIds.length} distinct users with at least one active wishlist (including empty wishlists)`);
-        return userIds;
-    }
+    /**
+     * Get user IDs from the wishlists processed in this run (non-empty).
+     * Keeps no-match notifications aligned with the actual matching scope.
+     */
+    private getAllWishlistUserIdsForNoMatch(wishlistData: WishlistData[]): string[] {
+        const userIds = [...new Set(wishlistData.map(w => w.userId))];
+        console.log(`📋 [no-match] Found ${userIds.length} users with non-empty wishlists processed in this run`);
+        return userIds;
+    }
🤖 Prompt for AI Agents
In `@platforms/dreamsync-api/src/services/AIMatchingService.ts` around lines 141 -
156, The current getAllWishlistUserIdsForNoMatch collects all active wishlist
owners (including empty wishlists) and ignores the run's 200‑wishlist processing
cap; change the query in getAllWishlistUserIdsForNoMatch (using
wishlistRepository.createQueryBuilder("wishlist")) to only include wishlists
that were actually analyzed: add a predicate to exclude empty wishlists (e.g.,
where wishlist.items IS NOT NULL or wishlist.itemCount > 0 or the appropriate
non-empty column for your schema) and apply the same limit/offset or 200‑item
cap used elsewhere in this run so the returned userIds exactly match the
processed, non‑empty wishlists. Ensure the distinct userId select remains and
keep the processLogger/console message updated if desired.

Comment on lines +722 to +732
const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$

Dear ${displayName},

DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.

we would encourage you to add more details to your wishlist and add more topics you are interested in.

Best Wishes,
DreamSync`;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Polish the no‑match message copy.
Minor grammar/capitalization and repeated phrasing in a user‑facing message.

✍️ Suggested copy edit
-            const messageContent = `$$system-message$$
-
-Dear ${displayName},
-
-DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.
-
-we would encourage you to add more details to your wishlist and add more topics you are interested in.
-
-Best Wishes,
-DreamSync`;
+            const messageContent = `$$system-message$$
+
+Dear ${displayName},
+
+DreamSync tried to find matches with other users using your wishlist, but no conclusive matches were found.
+
+We encourage you to add more details to your wishlist and include more topics you're interested in.
+
+Best wishes,
+DreamSync`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$
Dear ${displayName},
DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.
we would encourage you to add more details to your wishlist and add more topics you are interested in.
Best Wishes,
DreamSync`;
const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$
Dear ${displayName},
DreamSync tried to find matches with other users using your wishlist, but no conclusive matches were found.
We encourage you to add more details to your wishlist and include more topics you're interested in.
Best wishes,
DreamSync`;
🤖 Prompt for AI Agents
In `@platforms/dreamsync-api/src/services/MatchNotificationService.ts` around
lines 722 - 732, In MatchNotificationService update the no-match message string
assigned to messageContent (using displayName) to a clearer, grammatically
correct user-facing copy: replace the current body with a concise message such
as "Dear {displayName},\n\nWe couldn’t find any conclusive matches for your
wishlist this time. Try adding more details or topics to increase your chances
of finding a match.\n\nBest wishes,\nDreamSync" (preserve the $$system-message$$
wrapper if required), ensuring proper capitalization and removing repeated
phrasing.

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.

[feature] Create Message at Dreamsync when no match is found

3 participants