diff --git a/platforms/dreamsync-api/src/services/AIMatchingService.ts b/platforms/dreamsync-api/src/services/AIMatchingService.ts index b653c9e3..5075669a 100644 --- a/platforms/dreamsync-api/src/services/AIMatchingService.ts +++ b/platforms/dreamsync-api/src/services/AIMatchingService.ts @@ -131,9 +131,62 @@ export class AIMatchingService { // Process any existing matches that haven't been messaged yet await this.processUnmessagedMatches(); + + // Send no-match messages to ALL users with any non-null wishlist (even empty) who did not get a match this run + const allWishlistUserIds = await this.getAllWishlistUserIdsForNoMatch(); + 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 { + 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>(); + 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; + } + + /** + * Send no-match notifications to users who have a wishlist (even if empty) but did not get a match this run. + */ + private async sendNoMatchNotifications(allWishlistUserIds: string[], matchResults: MatchResult[]): Promise { + const matchedUserIds = new Set(matchResults.flatMap(r => r.userIds)); + const noMatchUserIds = allWishlistUserIds.filter(userId => !matchedUserIds.has(userId)); + + console.log(`📋 [no-match] Total wishlist users: ${allWishlistUserIds.length}, matched this run: ${matchedUserIds.size}, will send no-match to: ${noMatchUserIds.length}`); + + if (noMatchUserIds.length === 0) { + console.log("✅ [no-match] No users to notify (all wishlist users got a match this run)"); + return; + } + + console.log(`📨 [no-match] Sending no-match notifications to ${noMatchUserIds.length} users`); + let sentCount = 0; + let errorCount = 0; + 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); + } + } + console.log(`🎉 [no-match] Completed: ${sentCount} sent, ${errorCount} errors (total no-match candidates: ${noMatchUserIds.length})`); + } + /** * Find and process matches that haven't been messaged yet */ diff --git a/platforms/dreamsync-api/src/services/MatchNotificationService.ts b/platforms/dreamsync-api/src/services/MatchNotificationService.ts index 30e9f8ba..4be31773 100644 --- a/platforms/dreamsync-api/src/services/MatchNotificationService.ts +++ b/platforms/dreamsync-api/src/services/MatchNotificationService.ts @@ -684,6 +684,68 @@ Reply with the Match ID "${match.id}" to connect with the other ${otherUserIds.l } } + /** + * Send a no-match notification to a user who has a wishlist but did not get a match this run. + * Condition: they have made a wishlist (even if empty). + */ + async sendNoMatchNotification(userId: string): Promise { + try { + console.log(`📨 [no-match] sendNoMatchNotification: starting for user ${userId}`); + + const dreamsyncUser = await this.findDreamSyncUser(); + if (!dreamsyncUser) { + console.error("[no-match] Cannot send: DreamSync user not found"); + return; + } + + const user = await this.userService.getUserById(userId); + if (!user) { + console.error(`[no-match] Cannot send: User ${userId} not found`); + return; + } + console.log(`📨 [no-match] User found: ${userId} (${user.name || user.ename || "no name"})`); + + const chatResult = await this.findOrCreateMutualChat(userId); + if (!chatResult.chat) { + console.error(`[no-match] Cannot send: Could not find/create chat for user ${userId}`); + return; + } + console.log(`📨 [no-match] Chat ready: ${chatResult.chat.id} (wasCreated: ${chatResult.wasCreated})`); + + const { chat, wasCreated } = chatResult; + if (wasCreated) { + console.log(`⏳ [no-match] Chat was just created, waiting 15 seconds before sending...`); + await new Promise(resolve => setTimeout(resolve, 15000)); + console.log(`✅ [no-match] 15-second delay completed`); + } + + 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 messageRepository = AppDataSource.getRepository(Message); + const message = messageRepository.create({ + text: messageContent, + sender: dreamsyncUser, + group: chat, + isSystemMessage: true, + }); + + await messageRepository.save(message); + console.log(`✅ [no-match] Message saved for user ${userId} (messageId: ${message.id})`); + } catch (error) { + console.error(`❌ [no-match] Error sending no-match notification to user ${userId}:`, error); + } + } + /** * Process a new match and send notifications */