Closer/functions/dist/couples/scheduledOutcomesReminder.js

157 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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.scheduledOutcomesReminder = void 0;
const functions = __importStar(require("firebase-functions"));
const admin = __importStar(require("firebase-admin"));
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 () => {
var _a, _b;
const db = admin.firestore();
const messaging = admin.messaging();
const now = Date.now();
const couplesSnap = await db.collection('couples').limit(200).get();
const notifications = [];
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);
if (createdAt <= 0)
continue;
const ageDays = Math.floor((now - createdAt) / DAY_MS);
const dueDays = REMINDER_DAYS.filter((day) => ageDays >= day && ageDays <= day + 2);
if (dueDays.length === 0)
continue;
const userIds = ((_b = data.userIds) !== null && _b !== void 0 ? _b : []);
if (userIds.length === 0)
continue;
// Check each due checkpoint; only remind for the first one without an outcome.
let remindedDay = null;
for (const day of dueDays) {
const dayKey = DAY_KEY_MAP[day];
const outcomeSnap = await coupleDoc.ref.collection('outcomes').doc(dayKey).get();
if (!outcomeSnap.exists) {
remindedDay = day;
break;
}
}
if (remindedDay == null)
continue;
const dayLabel = remindedDay;
const title = 'How are you feeling together?';
const body = `Youve been connected for ${dayLabel} days. Take a quick check-in to see how things have changed.`;
for (const userId of userIds) {
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}`);
});
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) {
await db
.collection('users')
.doc(notification.userId)
.collection('notification_queue')
.add({
type: 'outcome_reminder',
title: notification.title,
body: notification.body,
coupleId: notification.coupleId,
day: notification.day,
read: false,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
});
const tokens = await getUserTokens(db, notification.userId);
if (tokens.length === 0) {
console.log(`[sendOutcomeReminder] no FCM tokens for ${notification.userId}`);
return;
}
const message = {
token: tokens[0],
notification: {
title: notification.title,
body: notification.body,
},
data: {
type: 'outcome_reminder',
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);
}
});
}
async function getUserTokens(db, userId) {
var _a;
const tokens = [];
const userDoc = await db.collection('users').doc(userId).get();
const legacyToken = (_a = userDoc.data()) === null || _a === void 0 ? void 0 : _a.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;
}
//# sourceMappingURL=scheduledOutcomesReminder.js.map