diff --git a/package.json b/package.json index 1a4d34eb..552b8e47 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "nodemon -w src/ -x tsx src/app.ts", "start": "tsx src/app.ts", + "subscribe:staff": "tsx scr/subscribe-staff-to-topic.ts", "lint": "eslint . --fix", "lint:check": "eslint .", "format": "prettier . --write", diff --git a/src/scripts/subscribe-staff-to-topic.ts b/src/scripts/subscribe-staff-to-topic.ts new file mode 100644 index 00000000..584a6328 --- /dev/null +++ b/src/scripts/subscribe-staff-to-topic.ts @@ -0,0 +1,59 @@ +import "dotenv/config"; +import { SupabaseDB } from "../database"; +import { getFirebaseAdmin } from "../firebase"; + +async function main() { + const topicName = "allStaff"; + // Ensure topic exists in Supabase customTopics + const { data: existing } = await SupabaseDB.CUSTOM_TOPICS.select( + "topicName" + ) + .eq("topicName", topicName) + .maybeSingle(); + + if (!existing) { + await SupabaseDB.CUSTOM_TOPICS.insert({ topicName }).throwOnError(); + console.log(`Created custom topic in Supabase: ${topicName}`); + } + + // Find all staff userIds from authRoles + const { data: staffRoles, error: staffErr } = + await SupabaseDB.AUTH_ROLES.select("userId") + .eq("role", "STAFF") + .throwOnError(); + if (staffErr) throw staffErr; + + const staffUserIds = (staffRoles ?? []).map( + (r: { userId: string }) => r.userId + ); + if (staffUserIds.length === 0) { + console.log("No staff userIds found. Nothing to subscribe."); + process.exit(0); + } + + const { data: userDevices } = await SupabaseDB.NOTIFICATIONS.select( + "userId, deviceId" + ) + .in("userId", staffUserIds) + .throwOnError(); + + const deviceTokens = (userDevices ?? []) + .map((d: { deviceId: string | null }) => d.deviceId) + .filter((t: string | null): t is string => Boolean(t)); + + if (deviceTokens.length === 0) { + console.log("No device tokens found for staff. Exiting."); + process.exit(0); + } + + const admin = getFirebaseAdmin(); + await admin.messaging().subscribeToTopic(deviceTokens, topicName); // we have like 50 people on staff + console.log( + `Done. Subscribed ${deviceTokens.length} device(s) for ${staffUserIds.length} staff.` + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index 7feb7b72..2b01755e 100644 --- a/src/services/subscription/subscription-router.ts +++ b/src/services/subscription/subscription-router.ts @@ -89,13 +89,20 @@ subscriptionRouter.post( }); } - // Get email addresses for all subscribed users + // need to batch to avoid URL length limits const userIds = subscriptions.map((sub) => sub.userId); - const { data: users } = await SupabaseDB.AUTH_INFO.select("email") - .in("userId", userIds) - .throwOnError(); + const BATCH_SIZE = 100; + const emailAddresses: string[] = []; + + for (let i = 0; i < userIds.length; i += BATCH_SIZE) { + const batch = userIds.slice(i, i + BATCH_SIZE); + const { data: users } = await SupabaseDB.AUTH_INFO.select("email") + .in("userId", batch) + .throwOnError(); - const emailAddresses = users?.map((user) => user.email) || []; + const batchEmails = users?.map((user) => user.email) || []; + emailAddresses.push(...batchEmails); + } const sendEmailCommand = new SendEmailCommand({ FromEmailAddress: Config.FROM_EMAIL_ADDRESS ?? "", @@ -168,13 +175,20 @@ subscriptionRouter.get( .json({ error: "No subscribers found for this mailing list." }); } - // Get email addresses for all subscribed users + // Get email addresses for all subscribed users (batch to avoid URL length limits) const userIds = subscriptions.map((sub) => sub.userId); - const { data: users } = await SupabaseDB.AUTH_INFO.select("email") - .in("userId", userIds) - .throwOnError(); + const BATCH_SIZE = 100; // Process in smaller batches + const emailAddresses: string[] = []; + + for (let i = 0; i < userIds.length; i += BATCH_SIZE) { + const batch = userIds.slice(i, i + BATCH_SIZE); + const { data: users } = await SupabaseDB.AUTH_INFO.select("email") + .in("userId", batch) + .throwOnError(); - const emailAddresses = users?.map((user) => user.email) || []; + const batchEmails = users?.map((user) => user.email) || []; + emailAddresses.push(...batchEmails); + } return res.status(StatusCodes.OK).json(emailAddresses); }