Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
59 changes: 59 additions & 0 deletions src/scripts/subscribe-staff-to-topic.ts
Original file line number Diff line number Diff line change
@@ -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);
});
34 changes: 24 additions & 10 deletions src/services/subscription/subscription-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? "",
Expand Down Expand Up @@ -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);
}
Expand Down