From 33ae195fcdcfd09be4e07666ed6ae3bd2646057f Mon Sep 17 00:00:00 2001 From: Bahl-Aryan Date: Tue, 16 Sep 2025 12:28:21 -0500 Subject: [PATCH 1/5] run staff topic script --- package.json | 1 + src/scripts/subscribe-staff-to-topic.ts | 70 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/scripts/subscribe-staff-to-topic.ts 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..7f68ba21 --- /dev/null +++ b/src/scripts/subscribe-staff-to-topic.ts @@ -0,0 +1,70 @@ +import { SupabaseDB } from "../database"; +import { getFirebaseAdmin } from "../firebase"; + +async function main() { + const [, , topicArg] = process.argv; + const topicName = topicArg?.trim(); + if (!topicName) { + console.error( + "Usage: tsx scripts/subscribe-staff-to-topic.ts " + ); + process.exit(1); + } + + // 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}`); + } else { + await SupabaseDB.CUSTOM_TOPICS.update({ topicName }) + .eq("topicName", topicName) + .throwOnError(); + } + + // 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); +}); From 90ef47485661ec6baff71534ef369233cb9f4d5a Mon Sep 17 00:00:00 2001 From: Bahl-Aryan Date: Tue, 16 Sep 2025 12:31:17 -0500 Subject: [PATCH 2/5] fixes --- src/scripts/subscribe-staff-to-topic.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/scripts/subscribe-staff-to-topic.ts b/src/scripts/subscribe-staff-to-topic.ts index 7f68ba21..d8d89a37 100644 --- a/src/scripts/subscribe-staff-to-topic.ts +++ b/src/scripts/subscribe-staff-to-topic.ts @@ -2,15 +2,7 @@ import { SupabaseDB } from "../database"; import { getFirebaseAdmin } from "../firebase"; async function main() { - const [, , topicArg] = process.argv; - const topicName = topicArg?.trim(); - if (!topicName) { - console.error( - "Usage: tsx scripts/subscribe-staff-to-topic.ts " - ); - process.exit(1); - } - + const topicName = "allStaff"; // Ensure topic exists in Supabase customTopics const { data: existing } = await SupabaseDB.CUSTOM_TOPICS.select( "topicName" @@ -21,10 +13,6 @@ async function main() { if (!existing) { await SupabaseDB.CUSTOM_TOPICS.insert({ topicName }).throwOnError(); console.log(`Created custom topic in Supabase: ${topicName}`); - } else { - await SupabaseDB.CUSTOM_TOPICS.update({ topicName }) - .eq("topicName", topicName) - .throwOnError(); } // Find all staff userIds from authRoles From 4b9a03660d8794fc8d64476b4db5f7cf32bb9d80 Mon Sep 17 00:00:00 2001 From: Bahl-Aryan Date: Tue, 16 Sep 2025 13:01:36 -0500 Subject: [PATCH 3/5] tiny fix --- src/scripts/subscribe-staff-to-topic.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/subscribe-staff-to-topic.ts b/src/scripts/subscribe-staff-to-topic.ts index d8d89a37..584a6328 100644 --- a/src/scripts/subscribe-staff-to-topic.ts +++ b/src/scripts/subscribe-staff-to-topic.ts @@ -1,3 +1,4 @@ +import "dotenv/config"; import { SupabaseDB } from "../database"; import { getFirebaseAdmin } from "../firebase"; From 4b2a70d3460fead8b27211fe76123b658fc4956a Mon Sep 17 00:00:00 2001 From: Bahl-Aryan Date: Thu, 18 Sep 2025 12:30:16 -0500 Subject: [PATCH 4/5] added email batching for subscription --- .../subscription/subscription-router.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index 7feb7b72..eadb97cf 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 + // 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 emailAddresses = users?.map((user) => user.email) || []; + 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 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 emailAddresses = users?.map((user) => user.email) || []; + 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 batchEmails = users?.map((user) => user.email) || []; + emailAddresses.push(...batchEmails); + } return res.status(StatusCodes.OK).json(emailAddresses); } From 2ea9ee48d13c83b94992ab328d0b5841e44ff96d Mon Sep 17 00:00:00 2001 From: Bahl-Aryan Date: Thu, 18 Sep 2025 12:32:15 -0500 Subject: [PATCH 5/5] formatted --- src/services/subscription/subscription-router.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index eadb97cf..2b01755e 100644 --- a/src/services/subscription/subscription-router.ts +++ b/src/services/subscription/subscription-router.ts @@ -89,17 +89,17 @@ subscriptionRouter.post( }); } - // Get email addresses for all subscribed users (batch to avoid URL length limits) + // need to batch to avoid URL length limits const userIds = subscriptions.map((sub) => sub.userId); - const BATCH_SIZE = 100; // Process in smaller batches + 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 batchEmails = users?.map((user) => user.email) || []; emailAddresses.push(...batchEmails); } @@ -179,13 +179,13 @@ subscriptionRouter.get( const userIds = subscriptions.map((sub) => sub.userId); 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 batchEmails = users?.map((user) => user.email) || []; emailAddresses.push(...batchEmails); }