Add Firebase Functions for automated prayer reminders
This commit is contained in:
1 parent
5ea16bba66
commit
a2ff235dcc
7 files changed
+10412
No files matched your search
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* Import function triggers from their respective submodules:
|
||||
*
|
||||
* import {onCall} from "firebase-functions/v2/https";
|
||||
* import {onDocumentWritten} from "firebase-functions/v2/firestore";
|
||||
*
|
||||
* See a full list of supported triggers at https://firebase.google.com/docs/functions
|
||||
*/
|
||||
|
||||
import {setGlobalOptions} from "firebase-functions";
|
||||
import {onRequest} from "firebase-functions/https";
|
||||
import {onSchedule} from "firebase-functions/v2/scheduler";
|
||||
import * as logger from "firebase-functions/logger";
|
||||
import * as admin from "firebase-admin";
|
||||
|
||||
// Initialize Firebase Admin
|
||||
admin.initializeApp();
|
||||
|
||||
// For cost control, you can set the maximum number of containers that can be
|
||||
// running at the same time. This helps mitigate the impact of unexpected
|
||||
// traffic spikes by instead downgrading performance. This limit is a
|
||||
// per-function limit. You can override the limit for each function using the
|
||||
// `maxInstances` option in the function's options, e.g.
|
||||
// `onRequest({ maxInstances: 5 }, (req, res) => { ... })`.
|
||||
// NOTE: setGlobalOptions does not apply to functions using the v1 API. V1
|
||||
// functions should each use functions.runWith({ maxInstances: 10 }) instead.
|
||||
// In the v1 API, each function can only serve one request per container, so
|
||||
// this will be the maximum concurrent request count.
|
||||
setGlobalOptions({ maxInstances: 10 });
|
||||
|
||||
// Daily prayer reminders function
|
||||
export const sendDailyPrayerReminders = onSchedule(
|
||||
{
|
||||
schedule: "0 9 * * *", // Run daily at 9 AM UTC
|
||||
timeZone: "America/New_York", // Adjust timezone as needed
|
||||
retryCount: 3,
|
||||
maxInstances: 1,
|
||||
},
|
||||
async (event) => {
|
||||
logger.info("Starting daily prayer reminders", {structuredData: true});
|
||||
|
||||
try {
|
||||
// Get all users who have notifications enabled
|
||||
const usersRef = admin.firestore().collection('users');
|
||||
const usersSnapshot = await usersRef
|
||||
.where('notificationsEnabled', '==', true)
|
||||
.where('fcmToken', '!=', null)
|
||||
.get();
|
||||
|
||||
logger.info(`Found ${usersSnapshot.size} users with notifications enabled`);
|
||||
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
// Send notifications to each user
|
||||
for (const userDoc of usersSnapshot.docs) {
|
||||
const userData = userDoc.data();
|
||||
const userId = userDoc.id;
|
||||
const fcmToken = userData.fcmToken;
|
||||
const reminderFrequency = userData.reminderFrequency || 'never';
|
||||
|
||||
// Skip if user doesn't want daily reminders
|
||||
if (reminderFrequency !== 'daily') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get user's active prayers for summary
|
||||
const prayersRef = admin.firestore().collection('prayers');
|
||||
const activePrayersSnapshot = await prayersRef
|
||||
.where('userId', '==', userId)
|
||||
.where('archived', '==', false)
|
||||
.where('answered', '==', false)
|
||||
.get();
|
||||
|
||||
const activePrayersCount = activePrayersSnapshot.size;
|
||||
const includeActiveSummary = userData.includeActiveSummary || false;
|
||||
|
||||
// Prepare notification message
|
||||
let notificationBody = "Time for your daily prayer reflection!";
|
||||
|
||||
if (includeActiveSummary && activePrayersCount > 0) {
|
||||
notificationBody += ` You have ${activePrayersCount} active prayer${activePrayersCount !== 1 ? 's' : ''} waiting for your attention.`;
|
||||
}
|
||||
|
||||
// Send FCM message
|
||||
const message = {
|
||||
token: fcmToken,
|
||||
notification: {
|
||||
title: "Daily Prayer Reminder",
|
||||
body: notificationBody,
|
||||
},
|
||||
data: {
|
||||
type: 'daily_reminder',
|
||||
userId: userId,
|
||||
activePrayersCount: activePrayersCount.toString(),
|
||||
},
|
||||
webpush: {
|
||||
fcmOptions: {
|
||||
link: "/", // Link to your app
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = await admin.messaging().send(message);
|
||||
logger.info(`Successfully sent daily reminder to user ${userId}`, {
|
||||
messageId: response,
|
||||
activePrayersCount
|
||||
});
|
||||
|
||||
successCount++;
|
||||
|
||||
// Update last email sent timestamp
|
||||
await usersRef.doc(userId).update({
|
||||
lastReminderSent: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send daily reminder to user ${userId}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
fcmToken: fcmToken.substring(0, 20) + '...' // Log partial token for debugging
|
||||
});
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Daily prayer reminders completed", {
|
||||
totalUsers: usersSnapshot.size,
|
||||
successCount,
|
||||
failureCount,
|
||||
structuredData: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Error in daily prayer reminders function", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
structuredData: true
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Weekly prayer reminders function
|
||||
export const sendWeeklyPrayerReminders = onSchedule(
|
||||
{
|
||||
schedule: "0 9 * * 1", // Run weekly on Monday at 9 AM UTC
|
||||
timeZone: "America/New_York",
|
||||
retryCount: 3,
|
||||
maxInstances: 1,
|
||||
},
|
||||
async (event) => {
|
||||
logger.info("Starting weekly prayer reminders", {structuredData: true});
|
||||
|
||||
try {
|
||||
const usersRef = admin.firestore().collection('users');
|
||||
const usersSnapshot = await usersRef
|
||||
.where('notificationsEnabled', '==', true)
|
||||
.where('fcmToken', '!=', null)
|
||||
.where('reminderFrequency', '==', 'weekly')
|
||||
.get();
|
||||
|
||||
logger.info(`Found ${usersSnapshot.size} users with weekly notifications enabled`);
|
||||
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
for (const userDoc of usersSnapshot.docs) {
|
||||
const userData = userDoc.data();
|
||||
const userId = userDoc.id;
|
||||
const fcmToken = userData.fcmToken;
|
||||
|
||||
try {
|
||||
const prayersRef = admin.firestore().collection('prayers');
|
||||
const activePrayersSnapshot = await prayersRef
|
||||
.where('userId', '==', userId)
|
||||
.where('archived', '==', false)
|
||||
.get();
|
||||
|
||||
const activePrayersCount = activePrayersSnapshot.size;
|
||||
const answeredPrayersCount = activePrayersSnapshot.docs.filter(doc => doc.data().answered).length;
|
||||
|
||||
const message = {
|
||||
token: fcmToken,
|
||||
notification: {
|
||||
title: "Weekly Prayer Reflection",
|
||||
body: `This week: ${answeredPrayersCount} prayers answered, ${activePrayersCount - answeredPrayersCount} still active. Take time to reflect and pray!`,
|
||||
},
|
||||
data: {
|
||||
type: 'weekly_reminder',
|
||||
userId: userId,
|
||||
activePrayersCount: (activePrayersCount - answeredPrayersCount).toString(),
|
||||
answeredPrayersCount: answeredPrayersCount.toString(),
|
||||
},
|
||||
webpush: {
|
||||
fcmOptions: {
|
||||
link: "/",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = await admin.messaging().send(message);
|
||||
logger.info(`Successfully sent weekly reminder to user ${userId}`, {
|
||||
messageId: response,
|
||||
activePrayersCount: activePrayersCount - answeredPrayersCount,
|
||||
answeredPrayersCount
|
||||
});
|
||||
|
||||
successCount++;
|
||||
|
||||
await usersRef.doc(userId).update({
|
||||
lastReminderSent: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send weekly reminder to user ${userId}`, {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Weekly prayer reminders completed", {
|
||||
totalUsers: usersSnapshot.size,
|
||||
successCount,
|
||||
failureCount,
|
||||
structuredData: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Error in weekly prayer reminders function", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
structuredData: true
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Monthly prayer reminders function
|
||||
export const sendMonthlyPrayerReminders = onSchedule(
|
||||
{
|
||||
schedule: "0 9 1 * *", // Run monthly on the 1st at 9 AM UTC
|
||||
timeZone: "America/New_York",
|
||||
retryCount: 3,
|
||||
maxInstances: 1,
|
||||
},
|
||||
async (event) => {
|
||||
logger.info("Starting monthly prayer reminders", {structuredData: true});
|
||||
|
||||
try {
|
||||
const usersRef = admin.firestore().collection('users');
|
||||
const usersSnapshot = await usersRef
|
||||
.where('notificationsEnabled', '==', true)
|
||||
.where('fcmToken', '!=', null)
|
||||
.where('reminderFrequency', '==', 'monthly')
|
||||
.get();
|
||||
|
||||
logger.info(`Found ${usersSnapshot.size} users with monthly notifications enabled`);
|
||||
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
for (const userDoc of usersSnapshot.docs) {
|
||||
const userData = userDoc.data();
|
||||
const userId = userDoc.id;
|
||||
const fcmToken = userData.fcmToken;
|
||||
|
||||
try {
|
||||
const prayersRef = admin.firestore().collection('prayers');
|
||||
const allPrayersSnapshot = await prayersRef
|
||||
.where('userId', '==', userId)
|
||||
.get();
|
||||
|
||||
const totalPrayers = allPrayersSnapshot.size;
|
||||
const answeredPrayers = allPrayersSnapshot.docs.filter(doc => doc.data().answered).length;
|
||||
const activePrayers = totalPrayers - answeredPrayers;
|
||||
|
||||
const message = {
|
||||
token: fcmToken,
|
||||
notification: {
|
||||
title: "Monthly Prayer Review",
|
||||
body: `This month: ${totalPrayers} total prayers, ${answeredPrayers} answered, ${activePrayers} still active. Reflect on God's faithfulness!`,
|
||||
},
|
||||
data: {
|
||||
type: 'monthly_reminder',
|
||||
userId: userId,
|
||||
totalPrayers: totalPrayers.toString(),
|
||||
answeredPrayers: answeredPrayers.toString(),
|
||||
activePrayers: activePrayers.toString(),
|
||||
},
|
||||
webpush: {
|
||||
fcmOptions: {
|
||||
link: "/",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = await admin.messaging().send(message);
|
||||
logger.info(`Successfully sent monthly reminder to user ${userId}`, {
|
||||
messageId: response,
|
||||
totalPrayers,
|
||||
answeredPrayers,
|
||||
activePrayers
|
||||
});
|
||||
|
||||
successCount++;
|
||||
|
||||
await usersRef.doc(userId).update({
|
||||
lastReminderSent: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send monthly reminder to user ${userId}`, {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Monthly prayer reminders completed", {
|
||||
totalUsers: usersSnapshot.size,
|
||||
successCount,
|
||||
failureCount,
|
||||
structuredData: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Error in monthly prayer reminders function", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
structuredData: true
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Test function for manual triggering
|
||||
export const testPrayerReminders = onRequest(async (req, res) => {
|
||||
logger.info("Manual test of prayer reminders triggered");
|
||||
|
||||
try {
|
||||
const usersRef = admin.firestore().collection('users');
|
||||
const usersSnapshot = await usersRef
|
||||
.where('notificationsEnabled', '==', true)
|
||||
.where('fcmToken', '!=', null)
|
||||
.limit(1) // Only test with first user
|
||||
.get();
|
||||
|
||||
if (usersSnapshot.empty) {
|
||||
res.json({ success: false, message: "No users with notifications enabled found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const userDoc = usersSnapshot.docs[0];
|
||||
const userData = userDoc.data();
|
||||
const fcmToken = userData.fcmToken;
|
||||
|
||||
const message = {
|
||||
token: fcmToken,
|
||||
notification: {
|
||||
title: "Test Prayer Reminder",
|
||||
body: "This is a test notification from your Prayer App!",
|
||||
},
|
||||
data: {
|
||||
type: 'test_reminder',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await admin.messaging().send(message);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Test notification sent successfully",
|
||||
messageId: response,
|
||||
userId: userDoc.id
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Error in test prayer reminders", { error: error instanceof Error ? error.message : String(error) });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "Failed to send test notification",
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user