diff --git a/functions/dist/couples/aggregateOutcomes.js b/functions/dist/couples/aggregateOutcomes.js index 132d9bb4..ace4b369 100644 --- a/functions/dist/couples/aggregateOutcomes.js +++ b/functions/dist/couples/aggregateOutcomes.js @@ -36,8 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.aggregateOutcomeStats = exports.MIN_COHORT = exports.FOLLOWUP_DAYS = exports.SCORE_KEYS = void 0; exports.aggregate = aggregate; exports.extractCoupleOutcome = extractCoupleOutcome; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); +const log_1 = require("../log"); exports.SCORE_KEYS = ['connection', 'communication', 'intimacy', 'happiness']; exports.FOLLOWUP_DAYS = ['day_30', 'day_60', 'day_90']; exports.MIN_COHORT = 50; @@ -83,7 +84,7 @@ function extractCoupleOutcome(outcomeDocs) { } return { deltas }; } -exports.aggregateOutcomeStats = functions.pubsub.schedule('every 24 hours').onRun(async () => { +exports.aggregateOutcomeStats = (0, scheduler_1.onSchedule)({ schedule: 'every 24 hours', memory: '512MiB', timeoutSeconds: 300 }, async () => { const db = admin.firestore(); const couples = []; const couplesSnap = await db.collection('couples').get(); @@ -93,8 +94,7 @@ exports.aggregateOutcomeStats = functions.pubsub.schedule('every 24 hours').onRu } const stats = aggregate(couples); await db.collection('aggregate_stats').doc('outcomes').set(Object.assign(Object.assign({}, stats), { totalCouples: couples.length, generatedAt: admin.firestore.Timestamp.now() })); - console.log(`[aggregateOutcomeStats] ${couples.length} couples → ` + + log_1.logger.log(`[aggregateOutcomeStats] ${couples.length} couples → ` + exports.FOLLOWUP_DAYS.map((d) => `${d}:${stats.windows[d].suppressed ? 'suppressed' : stats.windows[d].feltCloserPct + '%'}`).join(' ')); - return null; }); //# sourceMappingURL=aggregateOutcomes.js.map \ No newline at end of file diff --git a/functions/dist/couples/aggregateOutcomes.js.map b/functions/dist/couples/aggregateOutcomes.js.map index 7f92bc63..cc0c9d19 100644 --- a/functions/dist/couples/aggregateOutcomes.js.map +++ b/functions/dist/couples/aggregateOutcomes.js.map @@ -1 +1 @@ -{"version":3,"file":"aggregateOutcomes.js","sourceRoot":"","sources":["../../src/couples/aggregateOutcomes.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"aggregateOutcomes.js","sourceRoot":"","sources":["../../src/couples/aggregateOutcomes.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/couples/scheduledOutcomesReminder.js b/functions/dist/couples/scheduledOutcomesReminder.js index 129b7904..1c2d940b 100644 --- a/functions/dist/couples/scheduledOutcomesReminder.js +++ b/functions/dist/couples/scheduledOutcomesReminder.js @@ -34,16 +34,16 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.scheduledOutcomesReminder = void 0; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); const quietHours_1 = require("../notifications/quietHours"); -const pruneTokens_1 = require("../notifications/pruneTokens"); +const push_1 = require("../notifications/push"); +const time_1 = require("../notifications/time"); +const log_1 = require("../log"); const DAY_MS = 24 * 60 * 60 * 1000; const REMINDER_DAYS = [30, 60, 90]; const DAY_KEY_MAP = { 30: 'day_30', 60: 'day_60', 90: 'day_90' }; -exports.scheduledOutcomesReminder = functions.pubsub - .schedule('every 24 hours') - .onRun(async () => { +exports.scheduledOutcomesReminder = (0, scheduler_1.onSchedule)({ schedule: 'every 24 hours', timeoutSeconds: 180 }, async () => { var _a, _b; const db = admin.firestore(); const messaging = admin.messaging(); @@ -53,7 +53,7 @@ exports.scheduledOutcomesReminder = functions.pubsub for (const coupleDoc of couplesSnap.docs) { const coupleId = coupleDoc.id; const data = (_a = coupleDoc.data()) !== null && _a !== void 0 ? _a : {}; - const createdAt = millisFromFirestoreValue(data.createdAt); + const createdAt = (0, time_1.toMillis)(data.createdAt); if (createdAt <= 0) continue; const ageDays = Math.floor((now - createdAt) / DAY_MS); @@ -82,26 +82,16 @@ exports.scheduledOutcomesReminder = functions.pubsub notifications.push({ userId, coupleId, day: dayLabel, title, body }); } } - await Promise.all(notifications.map((notification) => sendOutcomeReminder(db, messaging, notification))); - console.log(`[scheduledOutcomesReminder] scanned ${couplesSnap.size}; notified ${notifications.length}`); + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled(notifications.map((notification) => sendOutcomeReminder(db, messaging, notification))); + log_1.logger.log(`[scheduledOutcomesReminder] scanned ${couplesSnap.size}; notified ${notifications.length}`); }); -function millisFromFirestoreValue(value) { - if (typeof value === 'number') - return value; - if (value instanceof admin.firestore.Timestamp) - return value.toMillis(); - if (value != null && - typeof value.toMillis === 'function') { - return value.toMillis(); - } - return 0; -} async function sendOutcomeReminder(db, messaging, notification) { const userDoc = await db.collection('users').doc(notification.userId).get(); const userData = userDoc.data(); // Honor the recipient's quiet hours (outcome check-ins are genuine, so no promotional gate). if ((0, quietHours_1.recipientInQuietHours)(userData)) { - console.log(`[sendOutcomeReminder] skip ${notification.userId} — quiet hours`); + log_1.logger.log(`[sendOutcomeReminder] skip ${notification.userId} — quiet hours`); return; } await db @@ -117,13 +107,7 @@ async function sendOutcomeReminder(db, messaging, notification) { read: false, createdAt: admin.firestore.FieldValue.serverTimestamp(), }); - const tokens = await getUserTokens(db, notification.userId, userData); - if (tokens.length === 0) { - console.log(`[sendOutcomeReminder] no FCM tokens for ${notification.userId}`); - return; - } - const message = { - token: tokens[0], + await (0, push_1.sendPushToUser)(db, messaging, notification.userId, { notification: { title: notification.title, body: notification.body, @@ -134,34 +118,6 @@ async function sendOutcomeReminder(db, messaging, notification) { coupleId: notification.coupleId, day: String(notification.day), }, - }; - const sendResults = await Promise.allSettled(tokens.map((token) => messaging.send(Object.assign(Object.assign({}, message), { token })))); - sendResults.forEach((result, index) => { - if (result.status === 'rejected') { - console.warn(`[sendOutcomeReminder] FCM send to ${tokens[index]} failed:`, result.reason); - } - }); - await (0, pruneTokens_1.pruneDeadTokens)(db, notification.userId, tokens, sendResults); -} -async function getUserTokens(db, userId, userData) { - const tokens = []; - const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(userId).get()).data(); - const legacyToken = data === null || data === void 0 ? void 0 : data.fcmToken; - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken); - } - const tokenSnapshot = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get(); - tokenSnapshot.docs.forEach((doc) => { - var _a; - const token = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; - if (typeof token === 'string' && token.length > 0 && !tokens.includes(token)) { - tokens.push(token); - } - }); - return tokens; + }, userData); } //# sourceMappingURL=scheduledOutcomesReminder.js.map \ No newline at end of file diff --git a/functions/dist/couples/scheduledOutcomesReminder.js.map b/functions/dist/couples/scheduledOutcomesReminder.js.map index e19ea4aa..997b0b3f 100644 --- a/functions/dist/couples/scheduledOutcomesReminder.js.map +++ b/functions/dist/couples/scheduledOutcomesReminder.js.map @@ -1 +1 @@ -{"version":3,"file":"scheduledOutcomesReminder.js","sourceRoot":"","sources":["../../src/couples/scheduledOutcomesReminder.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"scheduledOutcomesReminder.js","sourceRoot":"","sources":["../../src/couples/scheduledOutcomesReminder.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/notifications/dailyQuestionReminder.js b/functions/dist/notifications/dailyQuestionReminder.js index 188a8333..6d577e83 100644 --- a/functions/dist/notifications/dailyQuestionReminder.js +++ b/functions/dist/notifications/dailyQuestionReminder.js @@ -34,10 +34,11 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.sendDailyQuestionProactiveReminder = void 0; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); const quietHours_1 = require("./quietHours"); -const pruneTokens_1 = require("./pruneTokens"); +const push_1 = require("./push"); +const log_1 = require("../log"); /** * Proactive daily question reminder. * @@ -55,10 +56,7 @@ const pruneTokens_1 = require("./pruneTokens"); * 5. Record automated_daily_reminder/{docId} so this function is idempotent * on re-runs. */ -exports.sendDailyQuestionProactiveReminder = functions.pubsub - .schedule('0 16 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +exports.sendDailyQuestionProactiveReminder = (0, scheduler_1.onSchedule)({ schedule: '0 16 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, async () => { const db = admin.firestore(); const messaging = admin.messaging(); const now = Date.now(); @@ -71,7 +69,8 @@ exports.sendDailyQuestionProactiveReminder = functions.pubsub .get(); let notified = 0; let skipped = 0; - await Promise.all(expiringSnap.docs.map(async (questionDoc) => { + // Per-couple failures are isolated so one bad couple can't abort the whole run. + await Promise.allSettled(expiringSnap.docs.map(async (questionDoc) => { var _a, _b; const coupleRef = questionDoc.ref.parent.parent; if (!coupleRef) @@ -122,21 +121,21 @@ exports.sendDailyQuestionProactiveReminder = functions.pubsub return; } // Send FCM + notification_queue entry to every user in the couple. - await Promise.all(userIds.map((userId) => sendReminder(db, messaging, userId, coupleRef.id, dateKey))); + await Promise.allSettled(userIds.map((userId) => sendReminder(db, messaging, userId, coupleRef.id, dateKey))); notified += userIds.length; })); - console.log(`[sendDailyQuestionProactiveReminder] scanned ${expiringSnap.size} docs; notified ${notified} users; skipped ${skipped}`); + log_1.logger.log(`[sendDailyQuestionProactiveReminder] scanned ${expiringSnap.size} docs; notified ${notified} users; skipped ${skipped}`); }); async function sendReminder(db, messaging, userId, coupleId, questionDate) { const userDoc = await db.collection('users').doc(userId).get(); const userData = userDoc.data(); // Respect the user's Daily Reminder toggle (default on) and quiet hours. if ((userData === null || userData === void 0 ? void 0 : userData.notifDailyReminder) === false) { - console.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — daily reminder off`); + log_1.logger.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — daily reminder off`); return; } if ((0, quietHours_1.recipientInQuietHours)(userData)) { - console.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — quiet hours`); + log_1.logger.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — quiet hours`); return; } // In-app notification record. @@ -151,12 +150,7 @@ async function sendReminder(db, messaging, userId, coupleId, questionDate) { read: false, createdAt: admin.firestore.FieldValue.serverTimestamp(), }); - // FCM push. - const tokens = await getUserTokens(db, userId, userData); - if (tokens.length === 0) - return; - const sendResults = await Promise.allSettled(tokens.map((token) => messaging.send({ - token, + await (0, push_1.sendPushToUser)(db, messaging, userId, { notification: { title: "Tonight's question is waiting.", body: 'Answer together before it expires.', @@ -167,33 +161,6 @@ async function sendReminder(db, messaging, userId, coupleId, questionDate) { couple_id: coupleId, question_date: questionDate, }, - }))); - sendResults.forEach((result, i) => { - if (result.status === 'rejected') { - console.warn(`[sendDailyQuestionProactiveReminder] FCM failed for token ${tokens[i]}:`, result.reason); - } - }); - await (0, pruneTokens_1.pruneDeadTokens)(db, userId, tokens, sendResults); -} -async function getUserTokens(db, userId, userData) { - const tokens = []; - const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(userId).get()).data(); - const legacyToken = data === null || data === void 0 ? void 0 : data.fcmToken; - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken); - } - const tokenSnap = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get(); - tokenSnap.docs.forEach((doc) => { - var _a; - const t = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) { - tokens.push(t); - } - }); - return tokens; + }, userData); } //# sourceMappingURL=dailyQuestionReminder.js.map \ No newline at end of file diff --git a/functions/dist/notifications/dailyQuestionReminder.js.map b/functions/dist/notifications/dailyQuestionReminder.js.map index f406ea73..de685007 100644 --- a/functions/dist/notifications/dailyQuestionReminder.js.map +++ b/functions/dist/notifications/dailyQuestionReminder.js.map @@ -1 +1 @@ -{"version":3,"file":"dailyQuestionReminder.js","sourceRoot":"","sources":["../../src/notifications/dailyQuestionReminder.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dailyQuestionReminder.js","sourceRoot":"","sources":["../../src/notifications/dailyQuestionReminder.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAAuC;AACvC,+DAA4D;AAC5D,6CAAoD;AACpD,iCAAuC;AACvC,gCAA+B;AAE/B;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,kCAAkC,GAAG,IAAA,sBAAU,EAC1D,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,cAAc,EAAE,GAAG,EAAE,EAC5E,KAAK,IAAI,EAAE;IACT,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IAEvC,kFAAkF;IAClF,MAAM,YAAY,GAAG,MAAM,EAAE;SAC1B,eAAe,CAAC,gBAAgB,CAAC;SACjC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;SAClE,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC;SACjF,GAAG,EAAE,CAAA;IAER,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,gFAAgF;IAChF,MAAM,OAAO,CAAC,UAAU,CACtB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;;QAC1C,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAA;QAC/C,IAAI,CAAC,SAAS;YAAE,OAAM;QAEtB,MAAM,UAAU,GAAG,QAAQ,WAAW,CAAC,EAAE,EAAE,CAAA;QAC3C,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QAE3E,sDAAsD;QACtD,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,CAAA;QAC3C,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;YAAC,OAAM;QAAC,CAAC;QAE7C,mEAAmE;QACnE,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,CAAA,CAAC,qDAAqD;QACpF,MAAM,SAAS,GAAG,SAAS,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACvE,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAA;QACxC,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;YAAC,OAAM;QAAC,CAAC;QAE5C,+DAA+D;QAC/D,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QAC9E,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;YAAC,OAAM;QAAC,CAAC;QAE7C,wBAAwB;QACxB,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAA;QACvC,MAAM,OAAO,GAAG,CAAC,MAAA,MAAA,SAAS,CAAC,IAAI,EAAE,0CAAE,OAAO,mCAAI,EAAE,CAAa,CAAA;QAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;YAAC,OAAM;QAAC,CAAC;QAE/C,sCAAsC;QACtC,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;gBACnC,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;gBACvC,IAAI,KAAK,CAAC,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;gBACjD,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE;oBAClB,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,eAAe,EAAE;oBACpD,YAAY,EAAE,OAAO;iBACtB,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,EAAE,CAAA;YACT,OAAM;QACR,CAAC;QAED,mEAAmE;QACnE,MAAM,OAAO,CAAC,UAAU,CACtB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACrB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAC3D,CACF,CAAA;QACD,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAA;IAC5B,CAAC,CAAC,CACH,CAAA;IAED,YAAM,CAAC,GAAG,CACR,gDAAgD,YAAY,CAAC,IAAI,mBAAmB,QAAQ,mBAAmB,OAAO,EAAE,CACzH,CAAA;AACH,CAAC,CACF,CAAA;AAED,KAAK,UAAU,YAAY,CACzB,EAA6B,EAC7B,SAAoC,EACpC,MAAc,EACd,QAAgB,EAChB,YAAoB;IAEpB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAA;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;IAE/B,yEAAyE;IACzE,IAAI,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,kBAAkB,MAAK,KAAK,EAAE,CAAC;QAC3C,YAAM,CAAC,GAAG,CAAC,6CAA6C,MAAM,uBAAuB,CAAC,CAAA;QACtF,OAAM;IACR,CAAC;IACD,IAAI,IAAA,kCAAqB,EAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,YAAM,CAAC,GAAG,CAAC,6CAA6C,MAAM,gBAAgB,CAAC,CAAA;QAC/E,OAAM;IACR,CAAC;IAED,8BAA8B;IAC9B,MAAM,EAAE;SACL,UAAU,CAAC,OAAO,CAAC;SACnB,GAAG,CAAC,MAAM,CAAC;SACX,UAAU,CAAC,oBAAoB,CAAC;SAChC,GAAG,CAAC;QACH,IAAI,EAAE,yBAAyB;QAC/B,KAAK,EAAE,gCAAgC;QACvC,IAAI,EAAE,oCAAoC;QAC1C,IAAI,EAAE,KAAK;QACX,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,eAAe,EAAE;KACxD,CAAC,CAAA;IAEJ,MAAM,IAAA,qBAAc,EAClB,EAAE,EACF,SAAS,EACT,MAAM,EACN;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,gCAAgC;YACvC,IAAI,EAAE,oCAAoC;SAC3C;QACD,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ;QAC/D,IAAI,EAAE;YACJ,IAAI,EAAE,yBAAyB;YAC/B,SAAS,EAAE,QAAQ;YACnB,aAAa,EAAE,YAAY;SAC5B;KACF,EACD,QAAQ,CACT,CAAA;AACH,CAAC"} \ No newline at end of file diff --git a/functions/dist/notifications/gameRetention.js b/functions/dist/notifications/gameRetention.js index 6ddde0c7..e9d7d763 100644 --- a/functions/dist/notifications/gameRetention.js +++ b/functions/dist/notifications/gameRetention.js @@ -34,10 +34,11 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.sendChallengeDayReminders = exports.unlockDueMemoryCapsules = void 0; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); const quietHours_1 = require("./quietHours"); -const pruneTokens_1 = require("./pruneTokens"); +const push_1 = require("./push"); +const log_1 = require("../log"); const DAY_MS = 24 * 60 * 60 * 1000; const CHALLENGE_TITLES = { gratitude_week: { title: 'Gratitude Week', durationDays: 7 }, @@ -45,9 +46,7 @@ const CHALLENGE_TITLES = { quality_time: { title: 'Quality Time', durationDays: 7 }, deep_conversations: { title: 'Deep Conversations', durationDays: 7 }, }; -exports.unlockDueMemoryCapsules = functions.pubsub - .schedule('every 1 hours') - .onRun(async () => { +exports.unlockDueMemoryCapsules = (0, scheduler_1.onSchedule)('every 1 hours', async () => { const db = admin.firestore(); const messaging = admin.messaging(); const now = Date.now(); @@ -94,12 +93,11 @@ exports.unlockDueMemoryCapsules = functions.pubsub }); notifications.push(...capsuleNotifications); } - await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification))); - console.log(`[unlockDueMemoryCapsules] unlocked ${snapshot.size}; notified ${notifications.length}`); + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled(notifications.map((notification) => sendNotification(db, messaging, notification))); + log_1.logger.log(`[unlockDueMemoryCapsules] unlocked ${snapshot.size}; notified ${notifications.length}`); }); -exports.sendChallengeDayReminders = functions.pubsub - .schedule('every 24 hours') - .onRun(async () => { +exports.sendChallengeDayReminders = (0, scheduler_1.onSchedule)('every 24 hours', async () => { const db = admin.firestore(); const messaging = admin.messaging(); const now = Date.now(); @@ -166,8 +164,9 @@ exports.sendChallengeDayReminders = functions.pubsub }); notifications.push(...challengeNotifications); } - await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification))); - console.log(`[sendChallengeDayReminders] scanned ${snapshot.size}; notified ${notifications.length}`); + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled(notifications.map((notification) => sendNotification(db, messaging, notification))); + log_1.logger.log(`[sendChallengeDayReminders] scanned ${snapshot.size}; notified ${notifications.length}`); }); function reminderKey(userId, day) { return `${userId.replace(/[^\w-]/g, '_')}_${day}`; @@ -178,12 +177,12 @@ async function sendNotification(db, messaging, notification) { // Challenge-day reminders are retention nudges → respect the promotional opt-out (default on). // (Memory-capsule unlocks are a genuine couple event, so they are not promotional-gated.) if (notification.type === 'challenge_day_ready' && (userData === null || userData === void 0 ? void 0 : userData.notifPromotional) === false) { - console.log(`[sendNotification] skip ${notification.userId} — promotional off`); + log_1.logger.log(`[sendNotification] skip ${notification.userId} — promotional off`); return; } // Honor the recipient's quiet hours for every scheduled push. if ((0, quietHours_1.recipientInQuietHours)(userData)) { - console.log(`[sendNotification] skip ${notification.userId} — quiet hours`); + log_1.logger.log(`[sendNotification] skip ${notification.userId} — quiet hours`); return; } await db @@ -197,13 +196,7 @@ async function sendNotification(db, messaging, notification) { read: false, createdAt: admin.firestore.FieldValue.serverTimestamp(), }); - const tokens = await getUserTokens(db, notification.userId, userData); - if (tokens.length === 0) { - console.log(`[sendNotification] no FCM tokens for ${notification.userId}`); - return; - } - const message = { - token: tokens[0], + await (0, push_1.sendPushToUser)(db, messaging, notification.userId, { notification: { title: notification.title, body: notification.body, @@ -215,38 +208,6 @@ async function sendNotification(db, messaging, notification) { }, }, data: Object.assign({ type: notification.type }, notification.data), - }; - const sendResults = await Promise.allSettled(tokens.map((token) => messaging.send(Object.assign(Object.assign({}, message), { token })))); - const failures = []; - sendResults.forEach((result, index) => { - if (result.status === 'rejected') { - failures.push(`${tokens[index]}: ${String(result.reason)}`); - } - }); - if (failures.length > 0) { - console.error(`[sendNotification] some notifications failed:`, failures); - } - await (0, pruneTokens_1.pruneDeadTokens)(db, notification.userId, tokens, sendResults); -} -async function getUserTokens(db, userId, userData) { - const tokens = []; - const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(userId).get()).data(); - const legacyToken = data === null || data === void 0 ? void 0 : data.fcmToken; - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken); - } - const tokenSnapshot = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get(); - tokenSnapshot.docs.forEach((doc) => { - var _a; - const token = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; - if (typeof token === 'string' && token.length > 0 && !tokens.includes(token)) { - tokens.push(token); - } - }); - return tokens; + }, userData); } //# sourceMappingURL=gameRetention.js.map \ No newline at end of file diff --git a/functions/dist/notifications/gameRetention.js.map b/functions/dist/notifications/gameRetention.js.map index cc8ba1a7..23a1b358 100644 --- a/functions/dist/notifications/gameRetention.js.map +++ b/functions/dist/notifications/gameRetention.js.map @@ -1 +1 @@ -{"version":3,"file":"gameRetention.js","sourceRoot":"","sources":["../../src/notifications/gameRetention.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"gameRetention.js","sourceRoot":"","sources":["../../src/notifications/gameRetention.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/notifications/reengagement.js b/functions/dist/notifications/reengagement.js index a887d807..3b3673a1 100644 --- a/functions/dist/notifications/reengagement.js +++ b/functions/dist/notifications/reengagement.js @@ -34,10 +34,11 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.sendReengagementReminder = void 0; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); const quietHours_1 = require("./quietHours"); -const pruneTokens_1 = require("./pruneTokens"); +const push_1 = require("./push"); +const log_1 = require("../log"); const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000; const TEN_DAYS_MS = 10 * 24 * 60 * 60 * 1000; const REENGAGEMENT_COOLDOWN_MS = 3 * 24 * 60 * 60 * 1000; @@ -52,10 +53,7 @@ const REENGAGEMENT_COOLDOWN_MS = 3 * 24 * 60 * 60 * 1000; * * Requires a Firestore composite index on couples: lastAnsweredAt ASC. */ -exports.sendReengagementReminder = functions.pubsub - .schedule('0 12 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +exports.sendReengagementReminder = (0, scheduler_1.onSchedule)({ schedule: '0 12 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, async () => { const db = admin.firestore(); const messaging = admin.messaging(); const now = Date.now(); @@ -69,7 +67,8 @@ exports.sendReengagementReminder = functions.pubsub .get(); let notified = 0; let skipped = 0; - await Promise.all(snap.docs.map(async (coupleDoc) => { + // Per-couple failures are isolated so one bad couple can't abort the whole run. + await Promise.allSettled(snap.docs.map(async (coupleDoc) => { var _a; const data = coupleDoc.data(); const coupleId = coupleDoc.id; @@ -100,21 +99,21 @@ exports.sendReengagementReminder = functions.pubsub skipped++; return; } - await Promise.all(userIds.map((uid) => sendNudge(db, messaging, uid, coupleId))); + await Promise.allSettled(userIds.map((uid) => sendNudge(db, messaging, uid, coupleId))); notified += userIds.length; })); - console.log(`[sendReengagementReminder] scanned ${snap.size}; notified ${notified}; skipped ${skipped}`); + log_1.logger.log(`[sendReengagementReminder] scanned ${snap.size}; notified ${notified}; skipped ${skipped}`); }); async function sendNudge(db, messaging, userId, coupleId) { const userDoc = await db.collection('users').doc(userId).get(); const userData = userDoc.data(); // Re-engagement is a promotional nudge — respect the opt-out (default on) and quiet hours. if ((userData === null || userData === void 0 ? void 0 : userData.notifPromotional) === false) { - console.log(`[sendReengagementReminder] skip ${userId} — promotional off`); + log_1.logger.log(`[sendReengagementReminder] skip ${userId} — promotional off`); return; } if ((0, quietHours_1.recipientInQuietHours)(userData)) { - console.log(`[sendReengagementReminder] skip ${userId} — quiet hours`); + log_1.logger.log(`[sendReengagementReminder] skip ${userId} — quiet hours`); return; } await db.collection('users').doc(userId).collection('notification_queue').add({ @@ -124,11 +123,7 @@ async function sendNudge(db, messaging, userId, coupleId) { read: false, createdAt: admin.firestore.FieldValue.serverTimestamp(), }); - const tokens = await getUserTokens(db, userId, userData); - if (tokens.length === 0) - return; - const results = await Promise.allSettled(tokens.map((token) => messaging.send({ - token, + await (0, push_1.sendPushToUser)(db, messaging, userId, { notification: { title: "It's been a while.", body: "Tonight's question is a good reason to reconnect.", @@ -138,22 +133,6 @@ async function sendNudge(db, messaging, userId, coupleId) { type: 'reengagement', couple_id: coupleId, }, - }))); - await (0, pruneTokens_1.pruneDeadTokens)(db, userId, tokens, results); -} -async function getUserTokens(db, userId, userData) { - const tokens = []; - const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(userId).get()).data(); - const legacy = data === null || data === void 0 ? void 0 : data.fcmToken; - if (typeof legacy === 'string' && legacy.length > 0) - tokens.push(legacy); - const snap = await db.collection('users').doc(userId).collection('fcmTokens').get(); - snap.docs.forEach((doc) => { - var _a; - const t = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) - tokens.push(t); - }); - return tokens; + }, userData); } //# sourceMappingURL=reengagement.js.map \ No newline at end of file diff --git a/functions/dist/notifications/reengagement.js.map b/functions/dist/notifications/reengagement.js.map index f49673b6..b07312f1 100644 --- a/functions/dist/notifications/reengagement.js.map +++ b/functions/dist/notifications/reengagement.js.map @@ -1 +1 @@ -{"version":3,"file":"reengagement.js","sourceRoot":"","sources":["../../src/notifications/reengagement.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"reengagement.js","sourceRoot":"","sources":["../../src/notifications/reengagement.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/notifications/streakReminder.js b/functions/dist/notifications/streakReminder.js index 897b5e30..5c2486dc 100644 --- a/functions/dist/notifications/streakReminder.js +++ b/functions/dist/notifications/streakReminder.js @@ -34,10 +34,12 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.sendStreakReminder = void 0; -const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); const quietHours_1 = require("./quietHours"); -const pruneTokens_1 = require("./pruneTokens"); +const push_1 = require("./push"); +const time_1 = require("./time"); +const log_1 = require("../log"); /** * Streak reminder — an evening "don't lose your streak" nudge. * @@ -51,13 +53,10 @@ const pruneTokens_1 = require("./pruneTokens"); * local day — true per-timezone firing is a future refinement; quiet-hours suppression keeps a mistimed * fire from landing at a bad local hour. (Streak day-boundary note in the plan.) */ -exports.sendStreakReminder = functions.pubsub - .schedule('0 19 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +exports.sendStreakReminder = (0, scheduler_1.onSchedule)({ schedule: '0 19 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, async () => { const db = admin.firestore(); const messaging = admin.messaging(); - const todayKey = chicagoDateKey(new Date()); + const todayKey = (0, time_1.chicagoDateKey)(new Date()); const coupleSnap = await db.collection('couples').where('streakCount', '>', 0).get(); let notified = 0; let skipped = 0; @@ -70,8 +69,8 @@ exports.sendStreakReminder = functions.pubsub return; } // Already did a shared action today → the streak is safe, no nudge needed. - const lastMs = toMillis(couple.lastAnsweredAt); - if (lastMs > 0 && chicagoDateKey(new Date(lastMs)) === todayKey) { + const lastMs = (0, time_1.toMillis)(couple.lastAnsweredAt); + if (lastMs > 0 && (0, time_1.chicagoDateKey)(new Date(lastMs)) === todayKey) { skipped++; return; } @@ -99,20 +98,20 @@ exports.sendStreakReminder = functions.pubsub })); results.forEach((r) => { if (r.status === 'rejected') - console.warn('[sendStreakReminder] couple failed:', r.reason); + log_1.logger.warn('[sendStreakReminder] couple failed', { error: String(r.reason) }); }); - console.log(`[sendStreakReminder] scanned ${coupleSnap.size} streak couples; notified ${notified}; skipped ${skipped}`); + log_1.logger.log(`[sendStreakReminder] scanned ${coupleSnap.size} streak couples; notified ${notified}; skipped ${skipped}`); }); async function sendStreakNudge(db, messaging, userId, coupleId, streak, dateKey) { const userDoc = await db.collection('users').doc(userId).get(); const userData = userDoc.data(); // Respect the user's Streak Reminder toggle (default on) and quiet hours. if ((userData === null || userData === void 0 ? void 0 : userData.notifStreakReminder) === false) { - console.log(`[sendStreakReminder] skip ${userId} — streak reminder off`); + log_1.logger.log(`[sendStreakReminder] skip ${userId} — streak reminder off`); return; } if ((0, quietHours_1.recipientInQuietHours)(userData)) { - console.log(`[sendStreakReminder] skip ${userId} — quiet hours`); + log_1.logger.log(`[sendStreakReminder] skip ${userId} — quiet hours`); return; } const title = `🔥 Keep your ${streak}-day streak`; @@ -128,53 +127,10 @@ async function sendStreakNudge(db, messaging, userId, coupleId, streak, dateKey) read: false, createdAt: admin.firestore.FieldValue.serverTimestamp(), }); - const tokens = await getUserTokens(db, userId, userData); - if (tokens.length === 0) - return; - const sendResults = await Promise.allSettled(tokens.map((token) => messaging.send({ - token, + await (0, push_1.sendPushToUser)(db, messaging, userId, { notification: { title, body }, android: { notification: { channelId: 'reminders' } }, data: { type: 'streak', couple_id: coupleId, reminder_date: dateKey }, - }))); - sendResults.forEach((result, i) => { - if (result.status === 'rejected') { - console.warn(`[sendStreakReminder] FCM failed for token ${tokens[i]}:`, result.reason); - } - }); - await (0, pruneTokens_1.pruneDeadTokens)(db, userId, tokens, sendResults); -} -/** YYYY-MM-DD for the given instant in America/Chicago (DST-safe; en-CA gives ISO date order). */ -function chicagoDateKey(d) { - return new Intl.DateTimeFormat('en-CA', { - timeZone: 'America/Chicago', - year: 'numeric', - month: '2-digit', - day: '2-digit', - }).format(d); -} -function toMillis(value) { - if (value instanceof admin.firestore.Timestamp) - return value.toMillis(); - if (typeof value === 'number') - return value; - return 0; -} -async function getUserTokens(db, userId, userData) { - const tokens = []; - const data = userData !== null && userData !== void 0 ? userData : (await db.collection('users').doc(userId).get()).data(); - const legacyToken = data === null || data === void 0 ? void 0 : data.fcmToken; - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken); - } - const tokenSnap = await db.collection('users').doc(userId).collection('fcmTokens').get(); - tokenSnap.docs.forEach((doc) => { - var _a; - const t = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) { - tokens.push(t); - } - }); - return tokens; + }, userData); } //# sourceMappingURL=streakReminder.js.map \ No newline at end of file diff --git a/functions/dist/notifications/streakReminder.js.map b/functions/dist/notifications/streakReminder.js.map index 1bd36add..78716837 100644 --- a/functions/dist/notifications/streakReminder.js.map +++ b/functions/dist/notifications/streakReminder.js.map @@ -1 +1 @@ -{"version":3,"file":"streakReminder.js","sourceRoot":"","sources":["../../src/notifications/streakReminder.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"streakReminder.js","sourceRoot":"","sources":["../../src/notifications/streakReminder.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/notifications/time.js b/functions/dist/notifications/time.js new file mode 100644 index 00000000..5f259640 --- /dev/null +++ b/functions/dist/notifications/time.js @@ -0,0 +1,65 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.chicagoDateKey = chicagoDateKey; +exports.toMillis = toMillis; +const admin = __importStar(require("firebase-admin")); +/** + * Shared date/time helpers for the scheduled jobs, consolidated from per-file copies + * (streakReminder's chicagoDateKey/toMillis, scheduledOutcomesReminder's millisFromFirestoreValue). + * The CST assignment helpers in questions/assignDailyQuestion.ts are a separate, unit-tested set and + * stay there. + */ +/** YYYY-MM-DD for the given instant in America/Chicago (DST-safe; en-CA gives ISO date order). */ +function chicagoDateKey(d) { + return new Intl.DateTimeFormat('en-CA', { + timeZone: 'America/Chicago', + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(d); +} +/** Best-effort milliseconds from a Firestore Timestamp, a raw number, or any `{ toMillis() }` value. */ +function toMillis(value) { + if (typeof value === 'number') + return value; + if (value instanceof admin.firestore.Timestamp) + return value.toMillis(); + if (value != null && typeof value.toMillis === 'function') { + return value.toMillis(); + } + return 0; +} +//# sourceMappingURL=time.js.map \ No newline at end of file diff --git a/functions/dist/notifications/time.js.map b/functions/dist/notifications/time.js.map new file mode 100644 index 00000000..e94c3e87 --- /dev/null +++ b/functions/dist/notifications/time.js.map @@ -0,0 +1 @@ +{"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/notifications/time.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,wCAOC;AAGD,4BAOC;AA3BD,sDAAuC;AAEvC;;;;;GAKG;AAEH,kGAAkG;AAClG,SAAgB,cAAc,CAAC,CAAO;IACpC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QACtC,QAAQ,EAAE,iBAAiB;QAC3B,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,SAAS;KACf,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;AACd,CAAC;AAED,wGAAwG;AACxG,SAAgB,QAAQ,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,KAAK,YAAY,KAAK,CAAC,SAAS,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAA;IACvE,IAAI,KAAK,IAAI,IAAI,IAAI,OAAQ,KAAgC,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACtF,OAAQ,KAAoC,CAAC,QAAQ,EAAE,CAAA;IACzD,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC"} \ No newline at end of file diff --git a/functions/dist/questions/assignDailyQuestion.js b/functions/dist/questions/assignDailyQuestion.js index 592e6270..431806c7 100644 --- a/functions/dist/questions/assignDailyQuestion.js +++ b/functions/dist/questions/assignDailyQuestion.js @@ -39,6 +39,8 @@ exports.parseCstDate = parseCstDate; exports.timestampAt6PmCst = timestampAt6PmCst; const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); +const scheduler_1 = require("firebase-functions/v2/scheduler"); +const log_1 = require("../log"); /** * Scheduled function that assigns one daily question per couple every day. * @@ -52,16 +54,13 @@ const admin = __importStar(require("firebase-admin")); * Admin SDK bypasses Firestore rules; the `daily_question` doc is server-write * only (`allow write: if false` in rules). */ -exports.assignDailyQuestion = functions.pubsub - .schedule('0 23 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +exports.assignDailyQuestion = (0, scheduler_1.onSchedule)({ schedule: '0 23 * * *', timeZone: 'America/Chicago', memory: '512MiB', timeoutSeconds: 300 }, async () => { const db = admin.firestore(); const today = cstDateString(); const nextDay = nextCstDateString(); const questionId = await pickDailyQuestionId(today); if (!questionId) { - console.error('[assignDailyQuestion] questions pool not seeded — skipping assignment'); + log_1.logger.error('[assignDailyQuestion] questions pool not seeded — skipping assignment'); return; } const couplesSnap = await db.collection('couples').get(); @@ -88,11 +87,11 @@ exports.assignDailyQuestion = functions.pubsub // Already assigned for today — idempotent. return; } - console.error(`[assignDailyQuestion] failed for ${coupleId}:`, err); + log_1.logger.error(`[assignDailyQuestion] failed for ${coupleId}`, { error: String(err) }); } }); await Promise.all(writes); - console.log(`[assignDailyQuestion] assigned ${questionId} to ${couplesSnap.size} couples for ${today}`); + log_1.logger.log(`[assignDailyQuestion] assigned ${questionId} to ${couplesSnap.size} couples for ${today}`); }); /** * Callable function for immediate daily question assignment. diff --git a/functions/dist/questions/assignDailyQuestion.js.map b/functions/dist/questions/assignDailyQuestion.js.map index 593fde0b..d970bdc6 100644 --- a/functions/dist/questions/assignDailyQuestion.js.map +++ b/functions/dist/questions/assignDailyQuestion.js.map @@ -1 +1 @@ -{"version":3,"file":"assignDailyQuestion.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"assignDailyQuestion.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/src/couples/aggregateOutcomes.ts b/functions/src/couples/aggregateOutcomes.ts index 7d6c2a82..fed43681 100644 --- a/functions/src/couples/aggregateOutcomes.ts +++ b/functions/src/couples/aggregateOutcomes.ts @@ -1,5 +1,6 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' +import { logger } from '../log' /** * Cloud Function: aggregateOutcomeStats @@ -90,28 +91,30 @@ export function extractCoupleOutcome( return { deltas } } -export const aggregateOutcomeStats = functions.pubsub.schedule('every 24 hours').onRun(async () => { - const db = admin.firestore() +export const aggregateOutcomeStats = onSchedule( + { schedule: 'every 24 hours', memory: '512MiB', timeoutSeconds: 300 }, + async () => { + const db = admin.firestore() - const couples: CoupleOutcome[] = [] - const couplesSnap = await db.collection('couples').get() - for (const couple of couplesSnap.docs) { - const outcomesSnap = await couple.ref.collection('outcomes').get() - couples.push( - extractCoupleOutcome(outcomesSnap.docs.map((d) => ({ id: d.id, data: d.data() }))) + const couples: CoupleOutcome[] = [] + const couplesSnap = await db.collection('couples').get() + for (const couple of couplesSnap.docs) { + const outcomesSnap = await couple.ref.collection('outcomes').get() + couples.push( + extractCoupleOutcome(outcomesSnap.docs.map((d) => ({ id: d.id, data: d.data() }))) + ) + } + + const stats = aggregate(couples) + await db.collection('aggregate_stats').doc('outcomes').set({ + ...stats, + totalCouples: couples.length, + generatedAt: admin.firestore.Timestamp.now(), + }) + + logger.log( + `[aggregateOutcomeStats] ${couples.length} couples → ` + + FOLLOWUP_DAYS.map((d) => `${d}:${stats.windows[d].suppressed ? 'suppressed' : stats.windows[d].feltCloserPct + '%'}`).join(' ') ) } - - const stats = aggregate(couples) - await db.collection('aggregate_stats').doc('outcomes').set({ - ...stats, - totalCouples: couples.length, - generatedAt: admin.firestore.Timestamp.now(), - }) - - console.log( - `[aggregateOutcomeStats] ${couples.length} couples → ` + - FOLLOWUP_DAYS.map((d) => `${d}:${stats.windows[d].suppressed ? 'suppressed' : stats.windows[d].feltCloserPct + '%'}`).join(' ') - ) - return null -}) +) diff --git a/functions/src/couples/scheduledOutcomesReminder.ts b/functions/src/couples/scheduledOutcomesReminder.ts index d779a2d4..8b50818e 100644 --- a/functions/src/couples/scheduledOutcomesReminder.ts +++ b/functions/src/couples/scheduledOutcomesReminder.ts @@ -1,7 +1,9 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' import { recipientInQuietHours } from '../notifications/quietHours' -import { pruneDeadTokens } from '../notifications/pruneTokens' +import { sendPushToUser } from '../notifications/push' +import { toMillis } from '../notifications/time' +import { logger } from '../log' /** * Cloud Function: scheduledOutcomesReminder @@ -23,9 +25,9 @@ const DAY_MS = 24 * 60 * 60 * 1000 const REMINDER_DAYS = [30, 60, 90] as const const DAY_KEY_MAP: Record = { 30: 'day_30', 60: 'day_60', 90: 'day_90' } -export const scheduledOutcomesReminder = functions.pubsub - .schedule('every 24 hours') - .onRun(async () => { +export const scheduledOutcomesReminder = onSchedule( + { schedule: 'every 24 hours', timeoutSeconds: 180 }, + async () => { const db = admin.firestore() const messaging = admin.messaging() const now = Date.now() @@ -42,7 +44,7 @@ export const scheduledOutcomesReminder = functions.pubsub for (const coupleDoc of couplesSnap.docs) { const coupleId = coupleDoc.id const data = coupleDoc.data() ?? {} - const createdAt = millisFromFirestoreValue(data.createdAt) + const createdAt = toMillis(data.createdAt) if (createdAt <= 0) continue const ageDays = Math.floor((now - createdAt) / DAY_MS) @@ -73,26 +75,16 @@ export const scheduledOutcomesReminder = functions.pubsub } } - await Promise.all( + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled( notifications.map((notification) => sendOutcomeReminder(db, messaging, notification) ) ) - console.log(`[scheduledOutcomesReminder] scanned ${couplesSnap.size}; notified ${notifications.length}`) - }) - -function millisFromFirestoreValue(value: unknown): number { - if (typeof value === 'number') return value - if (value instanceof admin.firestore.Timestamp) return value.toMillis() - if ( - value != null && - typeof (value as { toMillis?: unknown }).toMillis === 'function' - ) { - return (value as { toMillis: () => number }).toMillis() + logger.log(`[scheduledOutcomesReminder] scanned ${couplesSnap.size}; notified ${notifications.length}`) } - return 0 -} +) async function sendOutcomeReminder( db: admin.firestore.Firestore, @@ -104,7 +96,7 @@ async function sendOutcomeReminder( // Honor the recipient's quiet hours (outcome check-ins are genuine, so no promotional gate). if (recipientInQuietHours(userData)) { - console.log(`[sendOutcomeReminder] skip ${notification.userId} — quiet hours`) + logger.log(`[sendOutcomeReminder] skip ${notification.userId} — quiet hours`) return } @@ -122,66 +114,22 @@ async function sendOutcomeReminder( createdAt: admin.firestore.FieldValue.serverTimestamp(), }) - const tokens = await getUserTokens(db, notification.userId, userData) - if (tokens.length === 0) { - console.log(`[sendOutcomeReminder] no FCM tokens for ${notification.userId}`) - return - } - - const message: admin.messaging.Message = { - token: tokens[0], - notification: { - title: notification.title, - body: notification.body, + await sendPushToUser( + db, + messaging, + notification.userId, + { + notification: { + title: notification.title, + body: notification.body, + }, + android: { notification: { channelId: 'reminders' } }, // E-OBS + data: { + type: 'outcome_reminder', + coupleId: notification.coupleId, + day: String(notification.day), + }, }, - android: { notification: { channelId: 'reminders' } }, // E-OBS - data: { - type: 'outcome_reminder', - coupleId: notification.coupleId, - day: String(notification.day), - }, - } - - const sendResults = await Promise.allSettled( - tokens.map((token) => messaging.send({ ...message, token })) + userData, ) - - sendResults.forEach((result, index) => { - if (result.status === 'rejected') { - console.warn( - `[sendOutcomeReminder] FCM send to ${tokens[index]} failed:`, - result.reason - ) - } - }) - - await pruneDeadTokens(db, notification.userId, tokens, sendResults) -} - -async function getUserTokens( - db: admin.firestore.Firestore, - userId: string, - userData?: admin.firestore.DocumentData -): Promise { - const tokens: string[] = [] - const data = userData ?? (await db.collection('users').doc(userId).get()).data() - const legacyToken = data?.fcmToken - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken) - } - - const tokenSnapshot = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get() - - tokenSnapshot.docs.forEach((doc) => { - const token = doc.data()?.token - if (typeof token === 'string' && token.length > 0 && !tokens.includes(token)) { - tokens.push(token) - } - }) - - return tokens } diff --git a/functions/src/notifications/dailyQuestionReminder.ts b/functions/src/notifications/dailyQuestionReminder.ts index 83455f10..6171dbf9 100644 --- a/functions/src/notifications/dailyQuestionReminder.ts +++ b/functions/src/notifications/dailyQuestionReminder.ts @@ -1,7 +1,8 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' import { recipientInQuietHours } from './quietHours' -import { pruneDeadTokens } from './pruneTokens' +import { sendPushToUser } from './push' +import { logger } from '../log' /** * Proactive daily question reminder. @@ -20,10 +21,9 @@ import { pruneDeadTokens } from './pruneTokens' * 5. Record automated_daily_reminder/{docId} so this function is idempotent * on re-runs. */ -export const sendDailyQuestionProactiveReminder = functions.pubsub - .schedule('0 16 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +export const sendDailyQuestionProactiveReminder = onSchedule( + { schedule: '0 16 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, + async () => { const db = admin.firestore() const messaging = admin.messaging() const now = Date.now() @@ -39,7 +39,8 @@ export const sendDailyQuestionProactiveReminder = functions.pubsub let notified = 0 let skipped = 0 - await Promise.all( + // Per-couple failures are isolated so one bad couple can't abort the whole run. + await Promise.allSettled( expiringSnap.docs.map(async (questionDoc) => { const coupleRef = questionDoc.ref.parent.parent if (!coupleRef) return @@ -82,7 +83,7 @@ export const sendDailyQuestionProactiveReminder = functions.pubsub } // Send FCM + notification_queue entry to every user in the couple. - await Promise.all( + await Promise.allSettled( userIds.map((userId) => sendReminder(db, messaging, userId, coupleRef.id, dateKey) ) @@ -91,10 +92,11 @@ export const sendDailyQuestionProactiveReminder = functions.pubsub }) ) - console.log( + logger.log( `[sendDailyQuestionProactiveReminder] scanned ${expiringSnap.size} docs; notified ${notified} users; skipped ${skipped}` ) - }) + } +) async function sendReminder( db: admin.firestore.Firestore, @@ -108,11 +110,11 @@ async function sendReminder( // Respect the user's Daily Reminder toggle (default on) and quiet hours. if (userData?.notifDailyReminder === false) { - console.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — daily reminder off`) + logger.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — daily reminder off`) return } if (recipientInQuietHours(userData)) { - console.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — quiet hours`) + logger.log(`[sendDailyQuestionProactiveReminder] skip ${userId} — quiet hours`) return } @@ -129,61 +131,22 @@ async function sendReminder( createdAt: admin.firestore.FieldValue.serverTimestamp(), }) - // FCM push. - const tokens = await getUserTokens(db, userId, userData) - if (tokens.length === 0) return - - const sendResults = await Promise.allSettled( - tokens.map((token) => - messaging.send({ - token, - notification: { - title: "Tonight's question is waiting.", - body: 'Answer together before it expires.', - }, - android: { notification: { channelId: 'reminders' } }, // E-OBS - data: { - type: 'daily_question_reminder', - couple_id: coupleId, - question_date: questionDate, - }, - }) - ) + await sendPushToUser( + db, + messaging, + userId, + { + notification: { + title: "Tonight's question is waiting.", + body: 'Answer together before it expires.', + }, + android: { notification: { channelId: 'reminders' } }, // E-OBS + data: { + type: 'daily_question_reminder', + couple_id: coupleId, + question_date: questionDate, + }, + }, + userData, ) - - sendResults.forEach((result, i) => { - if (result.status === 'rejected') { - console.warn( - `[sendDailyQuestionProactiveReminder] FCM failed for token ${tokens[i]}:`, - result.reason - ) - } - }) - - await pruneDeadTokens(db, userId, tokens, sendResults) -} - -async function getUserTokens( - db: admin.firestore.Firestore, - userId: string, - userData?: admin.firestore.DocumentData -): Promise { - const tokens: string[] = [] - const data = userData ?? (await db.collection('users').doc(userId).get()).data() - const legacyToken = data?.fcmToken - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken) - } - const tokenSnap = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get() - tokenSnap.docs.forEach((doc) => { - const t = doc.data()?.token - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) { - tokens.push(t) - } - }) - return tokens } diff --git a/functions/src/notifications/gameRetention.ts b/functions/src/notifications/gameRetention.ts index fd2e7612..85c9918d 100644 --- a/functions/src/notifications/gameRetention.ts +++ b/functions/src/notifications/gameRetention.ts @@ -1,7 +1,8 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' import { recipientInQuietHours } from './quietHours' -import { pruneDeadTokens } from './pruneTokens' +import { sendPushToUser } from './push' +import { logger } from '../log' const DAY_MS = 24 * 60 * 60 * 1000 @@ -22,143 +23,141 @@ interface QueuedNotification { data: Record } -export const unlockDueMemoryCapsules = functions.pubsub - .schedule('every 1 hours') - .onRun(async () => { - const db = admin.firestore() - const messaging = admin.messaging() - const now = Date.now() +export const unlockDueMemoryCapsules = onSchedule('every 1 hours', async () => { + const db = admin.firestore() + const messaging = admin.messaging() + const now = Date.now() - const snapshot = await db - .collectionGroup('capsules') - .where('status', '==', 'sealed') - .where('unlockAt', '<=', now) - .limit(100) - .get() + const snapshot = await db + .collectionGroup('capsules') + .where('status', '==', 'sealed') + .where('unlockAt', '<=', now) + .limit(100) + .get() - const notifications: QueuedNotification[] = [] + const notifications: QueuedNotification[] = [] - for (const capsuleDoc of snapshot.docs) { - const capsuleRef = capsuleDoc.ref - const coupleRef = capsuleRef.parent.parent - if (!coupleRef) continue + for (const capsuleDoc of snapshot.docs) { + const capsuleRef = capsuleDoc.ref + const coupleRef = capsuleRef.parent.parent + if (!coupleRef) continue - const capsuleNotifications = await db.runTransaction(async (tx) => { - const freshCapsule = await tx.get(capsuleRef) - const capsule = freshCapsule.data() ?? {} - if (capsule.status !== 'sealed' || Number(capsule.unlockAt ?? 0) > now) { - return [] as QueuedNotification[] - } + const capsuleNotifications = await db.runTransaction(async (tx) => { + const freshCapsule = await tx.get(capsuleRef) + const capsule = freshCapsule.data() ?? {} + if (capsule.status !== 'sealed' || Number(capsule.unlockAt ?? 0) > now) { + return [] as QueuedNotification[] + } - const coupleDoc = await tx.get(coupleRef) - const userIds = (coupleDoc.data()?.userIds ?? []) as string[] - if (userIds.length === 0) return [] as QueuedNotification[] + const coupleDoc = await tx.get(coupleRef) + const userIds = (coupleDoc.data()?.userIds ?? []) as string[] + if (userIds.length === 0) return [] as QueuedNotification[] - tx.update(capsuleRef, { - status: 'unlocked', - unlockedAt: now, - unlockNotifiedAt: admin.firestore.FieldValue.serverTimestamp(), - }) - - const capsuleId = capsuleRef.id - const coupleId = coupleRef.id - const title = typeof capsule.title === 'string' && capsule.title.trim().length > 0 - ? capsule.title.trim() - : 'A memory capsule' - - return userIds.map((userId) => ({ - userId, - type: 'memory_capsule_unlocked' as const, - title: 'Your memory capsule opened', - body: `${title} is ready to read together.`, - data: { couple_id: coupleId, capsule_id: capsuleId }, - })) + tx.update(capsuleRef, { + status: 'unlocked', + unlockedAt: now, + unlockNotifiedAt: admin.firestore.FieldValue.serverTimestamp(), }) - notifications.push(...capsuleNotifications) - } + const capsuleId = capsuleRef.id + const coupleId = coupleRef.id + const title = typeof capsule.title === 'string' && capsule.title.trim().length > 0 + ? capsule.title.trim() + : 'A memory capsule' - await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification))) - console.log(`[unlockDueMemoryCapsules] unlocked ${snapshot.size}; notified ${notifications.length}`) - }) + return userIds.map((userId) => ({ + userId, + type: 'memory_capsule_unlocked' as const, + title: 'Your memory capsule opened', + body: `${title} is ready to read together.`, + data: { couple_id: coupleId, capsule_id: capsuleId }, + })) + }) -export const sendChallengeDayReminders = functions.pubsub - .schedule('every 24 hours') - .onRun(async () => { - const db = admin.firestore() - const messaging = admin.messaging() - const now = Date.now() + notifications.push(...capsuleNotifications) + } - const snapshot = await db - .collectionGroup('challenges') - .where('status', '==', 'active') - .limit(100) - .get() + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled(notifications.map((notification) => sendNotification(db, messaging, notification))) + logger.log(`[unlockDueMemoryCapsules] unlocked ${snapshot.size}; notified ${notifications.length}`) +}) - const notifications: QueuedNotification[] = [] +export const sendChallengeDayReminders = onSchedule('every 24 hours', async () => { + const db = admin.firestore() + const messaging = admin.messaging() + const now = Date.now() - for (const challengeDoc of snapshot.docs) { - const challengeRef = challengeDoc.ref - const coupleRef = challengeRef.parent.parent - if (!coupleRef) continue + const snapshot = await db + .collectionGroup('challenges') + .where('status', '==', 'active') + .limit(100) + .get() - const challengeNotifications = await db.runTransaction(async (tx) => { - const freshChallenge = await tx.get(challengeRef) - const challenge = freshChallenge.data() ?? {} - if (challenge.status !== 'active') return [] as QueuedNotification[] + const notifications: QueuedNotification[] = [] - const startedAt = Number(challenge.startedAt ?? 0) - if (startedAt <= 0 || startedAt > now) return [] as QueuedNotification[] + for (const challengeDoc of snapshot.docs) { + const challengeRef = challengeDoc.ref + const coupleRef = challengeRef.parent.parent + if (!coupleRef) continue - const challengeId = typeof challenge.challengeId === 'string' - ? challenge.challengeId - : challengeRef.id - const catalogEntry = CHALLENGE_TITLES[challengeId] ?? { - title: 'Connection Challenge', - durationDays: 7, - } - const day = Math.floor((now - startedAt) / DAY_MS) + 1 - if (day < 1 || day > catalogEntry.durationDays) return [] as QueuedNotification[] + const challengeNotifications = await db.runTransaction(async (tx) => { + const freshChallenge = await tx.get(challengeRef) + const challenge = freshChallenge.data() ?? {} + if (challenge.status !== 'active') return [] as QueuedNotification[] - const coupleDoc = await tx.get(coupleRef) - const userIds = (coupleDoc.data()?.userIds ?? []) as string[] - if (userIds.length === 0) return [] as QueuedNotification[] + const startedAt = Number(challenge.startedAt ?? 0) + if (startedAt <= 0 || startedAt > now) return [] as QueuedNotification[] - const completions = (challenge.completions ?? {}) as Record - const reminderSent = (challenge.challengeReminderSent ?? {}) as Record - const dueUserIds = userIds.filter((userId) => { - const completedDays = completions[userId] ?? [] - const alreadyCompleted = completedDays.map(Number).includes(day) - const alreadySent = reminderSent[reminderKey(userId, day)] === true - return !alreadyCompleted && !alreadySent - }) + const challengeId = typeof challenge.challengeId === 'string' + ? challenge.challengeId + : challengeRef.id + const catalogEntry = CHALLENGE_TITLES[challengeId] ?? { + title: 'Connection Challenge', + durationDays: 7, + } + const day = Math.floor((now - startedAt) / DAY_MS) + 1 + if (day < 1 || day > catalogEntry.durationDays) return [] as QueuedNotification[] - if (dueUserIds.length === 0) return [] as QueuedNotification[] + const coupleDoc = await tx.get(coupleRef) + const userIds = (coupleDoc.data()?.userIds ?? []) as string[] + if (userIds.length === 0) return [] as QueuedNotification[] - const updates: Record = { - lastChallengeReminderAt: admin.firestore.FieldValue.serverTimestamp(), - } - dueUserIds.forEach((userId) => { - updates[`challengeReminderSent.${reminderKey(userId, day)}`] = true - }) - tx.update(challengeRef, updates) - - const coupleId = coupleRef.id - return dueUserIds.map((userId) => ({ - userId, - type: 'challenge_day_ready' as const, - title: `Day ${day} is ready`, - body: `${catalogEntry.title}: today's connection prompt is waiting.`, - data: { couple_id: coupleId, challenge_id: challengeId, day: String(day) }, - })) + const completions = (challenge.completions ?? {}) as Record + const reminderSent = (challenge.challengeReminderSent ?? {}) as Record + const dueUserIds = userIds.filter((userId) => { + const completedDays = completions[userId] ?? [] + const alreadyCompleted = completedDays.map(Number).includes(day) + const alreadySent = reminderSent[reminderKey(userId, day)] === true + return !alreadyCompleted && !alreadySent }) - notifications.push(...challengeNotifications) - } + if (dueUserIds.length === 0) return [] as QueuedNotification[] - await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification))) - console.log(`[sendChallengeDayReminders] scanned ${snapshot.size}; notified ${notifications.length}`) - }) + const updates: Record = { + lastChallengeReminderAt: admin.firestore.FieldValue.serverTimestamp(), + } + dueUserIds.forEach((userId) => { + updates[`challengeReminderSent.${reminderKey(userId, day)}`] = true + }) + tx.update(challengeRef, updates) + + const coupleId = coupleRef.id + return dueUserIds.map((userId) => ({ + userId, + type: 'challenge_day_ready' as const, + title: `Day ${day} is ready`, + body: `${catalogEntry.title}: today's connection prompt is waiting.`, + data: { couple_id: coupleId, challenge_id: challengeId, day: String(day) }, + })) + }) + + notifications.push(...challengeNotifications) + } + + // Per-recipient failures are isolated so one bad send can't abort the batch. + await Promise.allSettled(notifications.map((notification) => sendNotification(db, messaging, notification))) + logger.log(`[sendChallengeDayReminders] scanned ${snapshot.size}; notified ${notifications.length}`) +}) function reminderKey(userId: string, day: number): string { return `${userId.replace(/[^\w-]/g, '_')}_${day}` @@ -175,12 +174,12 @@ async function sendNotification( // Challenge-day reminders are retention nudges → respect the promotional opt-out (default on). // (Memory-capsule unlocks are a genuine couple event, so they are not promotional-gated.) if (notification.type === 'challenge_day_ready' && userData?.notifPromotional === false) { - console.log(`[sendNotification] skip ${notification.userId} — promotional off`) + logger.log(`[sendNotification] skip ${notification.userId} — promotional off`) return } // Honor the recipient's quiet hours for every scheduled push. if (recipientInQuietHours(userData)) { - console.log(`[sendNotification] skip ${notification.userId} — quiet hours`) + logger.log(`[sendNotification] skip ${notification.userId} — quiet hours`) return } @@ -196,72 +195,26 @@ async function sendNotification( createdAt: admin.firestore.FieldValue.serverTimestamp(), }) - const tokens = await getUserTokens(db, notification.userId, userData) - if (tokens.length === 0) { - console.log(`[sendNotification] no FCM tokens for ${notification.userId}`) - return - } - - const message: admin.messaging.Message = { - token: tokens[0], - notification: { - title: notification.title, - body: notification.body, - }, - // E-OBS: challenge reminders → Reminders channel; capsule-unlocked → partner-activity channel. - android: { + await sendPushToUser( + db, + messaging, + notification.userId, + { notification: { - channelId: notification.type === 'challenge_day_ready' ? 'reminders' : 'partner_activity', + title: notification.title, + body: notification.body, + }, + // E-OBS: challenge reminders → Reminders channel; capsule-unlocked → partner-activity channel. + android: { + notification: { + channelId: notification.type === 'challenge_day_ready' ? 'reminders' : 'partner_activity', + }, + }, + data: { + type: notification.type, + ...notification.data, }, }, - data: { - type: notification.type, - ...notification.data, - }, - } - - const sendResults = await Promise.allSettled( - tokens.map((token) => messaging.send({ ...message, token })) + userData, ) - - const failures: string[] = [] - sendResults.forEach((result, index) => { - if (result.status === 'rejected') { - failures.push(`${tokens[index]}: ${String(result.reason)}`) - } - }) - - if (failures.length > 0) { - console.error(`[sendNotification] some notifications failed:`, failures) - } - - await pruneDeadTokens(db, notification.userId, tokens, sendResults) -} - -async function getUserTokens( - db: admin.firestore.Firestore, - userId: string, - userData?: admin.firestore.DocumentData -): Promise { - const tokens: string[] = [] - const data = userData ?? (await db.collection('users').doc(userId).get()).data() - const legacyToken = data?.fcmToken - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken) - } - - const tokenSnapshot = await db - .collection('users') - .doc(userId) - .collection('fcmTokens') - .get() - - tokenSnapshot.docs.forEach((doc) => { - const token = doc.data()?.token - if (typeof token === 'string' && token.length > 0 && !tokens.includes(token)) { - tokens.push(token) - } - }) - - return tokens } diff --git a/functions/src/notifications/reengagement.ts b/functions/src/notifications/reengagement.ts index fe92e5c4..313fae6c 100644 --- a/functions/src/notifications/reengagement.ts +++ b/functions/src/notifications/reengagement.ts @@ -1,7 +1,8 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' import { recipientInQuietHours } from './quietHours' -import { pruneDeadTokens } from './pruneTokens' +import { sendPushToUser } from './push' +import { logger } from '../log' const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000 const TEN_DAYS_MS = 10 * 24 * 60 * 60 * 1000 @@ -18,10 +19,9 @@ const REENGAGEMENT_COOLDOWN_MS = 3 * 24 * 60 * 60 * 1000 * * Requires a Firestore composite index on couples: lastAnsweredAt ASC. */ -export const sendReengagementReminder = functions.pubsub - .schedule('0 12 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +export const sendReengagementReminder = onSchedule( + { schedule: '0 12 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, + async () => { const db = admin.firestore() const messaging = admin.messaging() const now = Date.now() @@ -38,7 +38,8 @@ export const sendReengagementReminder = functions.pubsub let notified = 0 let skipped = 0 - await Promise.all( + // Per-couple failures are isolated so one bad couple can't abort the whole run. + await Promise.allSettled( snap.docs.map(async (coupleDoc) => { const data = coupleDoc.data() const coupleId = coupleDoc.id @@ -65,13 +66,14 @@ export const sendReengagementReminder = functions.pubsub if (!claimed) { skipped++; return } - await Promise.all(userIds.map((uid) => sendNudge(db, messaging, uid, coupleId))) + await Promise.allSettled(userIds.map((uid) => sendNudge(db, messaging, uid, coupleId))) notified += userIds.length }) ) - console.log(`[sendReengagementReminder] scanned ${snap.size}; notified ${notified}; skipped ${skipped}`) - }) + logger.log(`[sendReengagementReminder] scanned ${snap.size}; notified ${notified}; skipped ${skipped}`) + } +) async function sendNudge( db: admin.firestore.Firestore, @@ -84,11 +86,11 @@ async function sendNudge( // Re-engagement is a promotional nudge — respect the opt-out (default on) and quiet hours. if (userData?.notifPromotional === false) { - console.log(`[sendReengagementReminder] skip ${userId} — promotional off`) + logger.log(`[sendReengagementReminder] skip ${userId} — promotional off`) return } if (recipientInQuietHours(userData)) { - console.log(`[sendReengagementReminder] skip ${userId} — quiet hours`) + logger.log(`[sendReengagementReminder] skip ${userId} — quiet hours`) return } @@ -100,42 +102,21 @@ async function sendNudge( createdAt: admin.firestore.FieldValue.serverTimestamp(), }) - const tokens = await getUserTokens(db, userId, userData) - if (tokens.length === 0) return - - const results = await Promise.allSettled( - tokens.map((token) => - messaging.send({ - token, - notification: { - title: "It's been a while.", - body: "Tonight's question is a good reason to reconnect.", - }, - android: { notification: { channelId: 'reminders' } }, // E-OBS - data: { - type: 'reengagement', - couple_id: coupleId, - }, - }) - ) + await sendPushToUser( + db, + messaging, + userId, + { + notification: { + title: "It's been a while.", + body: "Tonight's question is a good reason to reconnect.", + }, + android: { notification: { channelId: 'reminders' } }, // E-OBS + data: { + type: 'reengagement', + couple_id: coupleId, + }, + }, + userData, ) - await pruneDeadTokens(db, userId, tokens, results) -} - -async function getUserTokens( - db: admin.firestore.Firestore, - userId: string, - userData?: admin.firestore.DocumentData -): Promise { - const tokens: string[] = [] - const data = userData ?? (await db.collection('users').doc(userId).get()).data() - const legacy = data?.fcmToken - if (typeof legacy === 'string' && legacy.length > 0) tokens.push(legacy) - - const snap = await db.collection('users').doc(userId).collection('fcmTokens').get() - snap.docs.forEach((doc) => { - const t = doc.data()?.token - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) tokens.push(t) - }) - return tokens } diff --git a/functions/src/notifications/streakReminder.ts b/functions/src/notifications/streakReminder.ts index d52d03e6..1e57d465 100644 --- a/functions/src/notifications/streakReminder.ts +++ b/functions/src/notifications/streakReminder.ts @@ -1,7 +1,9 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { onSchedule } from 'firebase-functions/v2/scheduler' import { recipientInQuietHours } from './quietHours' -import { pruneDeadTokens } from './pruneTokens' +import { sendPushToUser } from './push' +import { chicagoDateKey, toMillis } from './time' +import { logger } from '../log' /** * Streak reminder — an evening "don't lose your streak" nudge. @@ -16,10 +18,9 @@ import { pruneDeadTokens } from './pruneTokens' * local day — true per-timezone firing is a future refinement; quiet-hours suppression keeps a mistimed * fire from landing at a bad local hour. (Streak day-boundary note in the plan.) */ -export const sendStreakReminder = functions.pubsub - .schedule('0 19 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +export const sendStreakReminder = onSchedule( + { schedule: '0 19 * * *', timeZone: 'America/Chicago', timeoutSeconds: 180 }, + async () => { const db = admin.firestore() const messaging = admin.messaging() const todayKey = chicagoDateKey(new Date()) @@ -59,13 +60,14 @@ export const sendStreakReminder = functions.pubsub }) ) results.forEach((r) => { - if (r.status === 'rejected') console.warn('[sendStreakReminder] couple failed:', r.reason) + if (r.status === 'rejected') logger.warn('[sendStreakReminder] couple failed', { error: String(r.reason) }) }) - console.log( + logger.log( `[sendStreakReminder] scanned ${coupleSnap.size} streak couples; notified ${notified}; skipped ${skipped}` ) - }) + } +) async function sendStreakNudge( db: admin.firestore.Firestore, @@ -80,11 +82,11 @@ async function sendStreakNudge( // Respect the user's Streak Reminder toggle (default on) and quiet hours. if (userData?.notifStreakReminder === false) { - console.log(`[sendStreakReminder] skip ${userId} — streak reminder off`) + logger.log(`[sendStreakReminder] skip ${userId} — streak reminder off`) return } if (recipientInQuietHours(userData)) { - console.log(`[sendStreakReminder] skip ${userId} — quiet hours`) + logger.log(`[sendStreakReminder] skip ${userId} — quiet hours`) return } @@ -103,61 +105,15 @@ async function sendStreakNudge( createdAt: admin.firestore.FieldValue.serverTimestamp(), }) - const tokens = await getUserTokens(db, userId, userData) - if (tokens.length === 0) return - - const sendResults = await Promise.allSettled( - tokens.map((token) => - messaging.send({ - token, - notification: { title, body }, - android: { notification: { channelId: 'reminders' } }, - data: { type: 'streak', couple_id: coupleId, reminder_date: dateKey }, - }) - ) + await sendPushToUser( + db, + messaging, + userId, + { + notification: { title, body }, + android: { notification: { channelId: 'reminders' } }, + data: { type: 'streak', couple_id: coupleId, reminder_date: dateKey }, + }, + userData, ) - sendResults.forEach((result, i) => { - if (result.status === 'rejected') { - console.warn(`[sendStreakReminder] FCM failed for token ${tokens[i]}:`, result.reason) - } - }) - - await pruneDeadTokens(db, userId, tokens, sendResults) -} - -/** YYYY-MM-DD for the given instant in America/Chicago (DST-safe; en-CA gives ISO date order). */ -function chicagoDateKey(d: Date): string { - return new Intl.DateTimeFormat('en-CA', { - timeZone: 'America/Chicago', - year: 'numeric', - month: '2-digit', - day: '2-digit', - }).format(d) -} - -function toMillis(value: unknown): number { - if (value instanceof admin.firestore.Timestamp) return value.toMillis() - if (typeof value === 'number') return value - return 0 -} - -async function getUserTokens( - db: admin.firestore.Firestore, - userId: string, - userData?: admin.firestore.DocumentData -): Promise { - const tokens: string[] = [] - const data = userData ?? (await db.collection('users').doc(userId).get()).data() - const legacyToken = data?.fcmToken - if (typeof legacyToken === 'string' && legacyToken.length > 0) { - tokens.push(legacyToken) - } - const tokenSnap = await db.collection('users').doc(userId).collection('fcmTokens').get() - tokenSnap.docs.forEach((doc) => { - const t = doc.data()?.token - if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) { - tokens.push(t) - } - }) - return tokens } diff --git a/functions/src/notifications/time.ts b/functions/src/notifications/time.ts new file mode 100644 index 00000000..5c27ccd5 --- /dev/null +++ b/functions/src/notifications/time.ts @@ -0,0 +1,28 @@ +import * as admin from 'firebase-admin' + +/** + * Shared date/time helpers for the scheduled jobs, consolidated from per-file copies + * (streakReminder's chicagoDateKey/toMillis, scheduledOutcomesReminder's millisFromFirestoreValue). + * The CST assignment helpers in questions/assignDailyQuestion.ts are a separate, unit-tested set and + * stay there. + */ + +/** YYYY-MM-DD for the given instant in America/Chicago (DST-safe; en-CA gives ISO date order). */ +export function chicagoDateKey(d: Date): string { + return new Intl.DateTimeFormat('en-CA', { + timeZone: 'America/Chicago', + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(d) +} + +/** Best-effort milliseconds from a Firestore Timestamp, a raw number, or any `{ toMillis() }` value. */ +export function toMillis(value: unknown): number { + if (typeof value === 'number') return value + if (value instanceof admin.firestore.Timestamp) return value.toMillis() + if (value != null && typeof (value as { toMillis?: unknown }).toMillis === 'function') { + return (value as { toMillis: () => number }).toMillis() + } + return 0 +} diff --git a/functions/src/questions/assignDailyQuestion.ts b/functions/src/questions/assignDailyQuestion.ts index 8aac15f0..5a7adbf8 100644 --- a/functions/src/questions/assignDailyQuestion.ts +++ b/functions/src/questions/assignDailyQuestion.ts @@ -1,6 +1,7 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' - +import { onSchedule } from 'firebase-functions/v2/scheduler' +import { logger } from '../log' /** * Scheduled function that assigns one daily question per couple every day. @@ -15,17 +16,16 @@ import * as admin from 'firebase-admin' * Admin SDK bypasses Firestore rules; the `daily_question` doc is server-write * only (`allow write: if false` in rules). */ -export const assignDailyQuestion = functions.pubsub - .schedule('0 23 * * *') - .timeZone('America/Chicago') - .onRun(async () => { +export const assignDailyQuestion = onSchedule( + { schedule: '0 23 * * *', timeZone: 'America/Chicago', memory: '512MiB', timeoutSeconds: 300 }, + async () => { const db = admin.firestore() const today = cstDateString() const nextDay = nextCstDateString() const questionId = await pickDailyQuestionId(today) if (!questionId) { - console.error('[assignDailyQuestion] questions pool not seeded — skipping assignment') + logger.error('[assignDailyQuestion] questions pool not seeded — skipping assignment') return } @@ -53,15 +53,16 @@ export const assignDailyQuestion = functions.pubsub // Already assigned for today — idempotent. return } - console.error(`[assignDailyQuestion] failed for ${coupleId}:`, err) + logger.error(`[assignDailyQuestion] failed for ${coupleId}`, { error: String(err) }) } }) await Promise.all(writes) - console.log( + logger.log( `[assignDailyQuestion] assigned ${questionId} to ${couplesSnap.size} couples for ${today}` ) - }) + } +) /** * Callable function for immediate daily question assignment.