diff --git a/app/lib/firebaseAdminWrapper.ts b/app/lib/firebaseAdminWrapper.ts index 238dfb5d..279b67d5 100644 --- a/app/lib/firebaseAdminWrapper.ts +++ b/app/lib/firebaseAdminWrapper.ts @@ -36,7 +36,8 @@ export function overrideFirestore(f: FirebaseFirestore.Firestore | null) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/strict-boolean-expressions -const firestore = () => OVERRIDE_FIRESTORE || getFirestore(getAdminApp()); +export const firestore = () => + OVERRIDE_FIRESTORE || getFirestore(getAdminApp()); let OVERRIDE_TO_FIRESTORE: ((data: unknown) => Record) | null; diff --git a/app/lib/notifications.ts b/app/lib/notifications.ts index 283f6af7..cdce66ef 100644 --- a/app/lib/notifications.ts +++ b/app/lib/notifications.ts @@ -1,5 +1,6 @@ +import { Timestamp } from 'firebase-admin/firestore'; import { CommentWithRepliesT, DBPuzzleT, FollowersV } from './dbtypes.js'; -import { getCollection } from './firebaseAdminWrapper.js'; +import { firestore, getCollection } from './firebaseAdminWrapper.js'; import { NewPuzzleNotificationT, NotificationT, @@ -98,3 +99,45 @@ export async function notificationsForPuzzleChange( return notifications; } + +export async function cleanNotifications() { + const today = new Date(); + const cleanOlder = new Date(new Date().setDate(today.getDate() - 20)); + const db = firestore(); + const collectionRef = db + .collection('n') + .where('t', '<', Timestamp.fromDate(cleanOlder)); + const query = collectionRef.limit(500); + + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable + deleteQueryBatch(db, query, resolve).catch(reject); + }); +} + +async function deleteQueryBatch( + db: FirebaseFirestore.Firestore, + query: FirebaseFirestore.Query, + resolve: (value: unknown) => void +) { + const snapshot = await query.get(); + + const batchSize = snapshot.size; + if (batchSize === 0) { + // When there are no documents left, we are done + resolve(0); + return; + } + + // Delete documents in a batch + const batch = db.batch(); + snapshot.docs.forEach((doc) => { + batch.delete(doc.ref); + }); + console.log('deleting batch'); + await batch.commit(); + + // Recurse on the next process tick, to avoid + // exploding the stack. + process.nextTick(() => deleteQueryBatch(db, query, resolve)); +} diff --git a/functions/src/index.ts b/functions/src/index.ts index 27dd3ec7..c295c83a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -24,8 +24,8 @@ import { import { Timestamp } from '../../app/lib/timestamp.js'; import { PathReporter } from '../../app/lib/pathReporter.js'; import { ReactionT, ReactionV } from '../../app/lib/reactions.js'; -import { getClient, sendEmail } from '../../app/lib/email.js'; import firestore from '@google-cloud/firestore'; +import { cleanNotifications } from '../../app/lib/notifications.js'; export const ratings = functions .runWith({ timeoutSeconds: 540, memory: '512MB' }) @@ -189,29 +189,15 @@ export const scheduledFirestoreExport = functions.pubsub ); }); -export const sendTestEmail = functions.pubsub - .schedule('0 0 1 1 *') - .onRun(async () => { - const client = await getClient(); - console.log('sending test'); - await sendEmail({ - client, - userId: 'fSEwJorvqOMK5UhNMHa4mu48izl1', - subject: 'Testing an email via SES', - markdown: '**Here** is the body', - oneClickUnsubscribeTag: 'all', - campaign: 'test', - }); - console.log('done'); - }); - export const notificationsSend = functions .runWith({ timeoutSeconds: 540 }) .pubsub.schedule('every day 16:00') .onRun(async () => { console.log('queuing emails'); await queueEmails(); - console.log('queued'); + console.log('done, cleaning old'); + await cleanNotifications(); + console.log('done'); }); export const puzzleUpdate = functions.firestore