diff --git a/apps/admin-dashboard/app/routes/_dashboard.students.remove.tsx b/apps/admin-dashboard/app/routes/_dashboard.students.remove.tsx index 37d4511d2..1d82bf73a 100644 --- a/apps/admin-dashboard/app/routes/_dashboard.students.remove.tsx +++ b/apps/admin-dashboard/app/routes/_dashboard.students.remove.tsx @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/react-router'; import { type ActionFunctionArgs, data, @@ -10,7 +9,6 @@ import { import z from 'zod'; import { job } from '@oyster/core/bull'; -import { db } from '@oyster/db'; import { Button, ErrorMessage, @@ -54,10 +52,18 @@ export async function action({ request }: ActionFunctionArgs) { return data(result, { status: 400 }); } - const count = await removeMembers(result.data.memberIds); + const ids = result.data.memberIds; + + const batches = splitArray(ids, 10); + + for (const batch of batches) { + job('student.batch_remove', { + memberIds: batch, + }); + } toast(session, { - message: `Removed ${count} members.`, + message: `Removing ${ids.length} members asynchronously.`, }); return redirect(Route['/students'], { @@ -67,38 +73,6 @@ export async function action({ request }: ActionFunctionArgs) { }); } -async function removeMembers(ids: string[]): Promise { - const batches = splitArray(ids, 10); - - let count = 0; - - for (const batch of batches) { - try { - const students = await db - .deleteFrom('students') - .where('id', 'in', batch) - .returning(['airtableId', 'email', 'firstName', 'slackId']) - .execute(); - - for (const student of students) { - job('student.removed', { - airtableId: student.airtableId as string, - email: student.email, - firstName: student.firstName, - sendViolationEmail: false, - slackId: student.slackId, - }); - } - - count += students.length; - } catch (e) { - Sentry.captureException(e); - } - } - - return count; -} - export default function RemoveMembersPage() { const { error, errors } = getErrors(useActionData()); @@ -115,9 +89,9 @@ export default function RemoveMembersPage() { - Note: These members will immediately be removed from our database, but - it may take some time for them to be removed from Slack, Mailchimp and - Airtable. + Note: This process will run asynchronously and if there are a lot of + members to remove, it may take several hours to fully remove them from + Slack, Mailchimp and Airtable.
diff --git a/packages/core/src/infrastructure/bull.types.ts b/packages/core/src/infrastructure/bull.types.ts index d4ff751a2..2b1eb8860 100644 --- a/packages/core/src/infrastructure/bull.types.ts +++ b/packages/core/src/infrastructure/bull.types.ts @@ -620,6 +620,12 @@ export const StudentBullJob = z.discriminatedUnion('name', [ name: z.literal('student.anniversary.email'), data: z.object({}), }), + z.object({ + name: z.literal('student.batch_remove'), + data: z.object({ + memberIds: z.array(Student.shape.id), + }), + }), z.object({ name: z.literal('student.birthdate.daily'), data: z.object({}), diff --git a/packages/core/src/modules/members/members.worker.ts b/packages/core/src/modules/members/members.worker.ts index 681db3df9..e8e501970 100644 --- a/packages/core/src/modules/members/members.worker.ts +++ b/packages/core/src/modules/members/members.worker.ts @@ -15,6 +15,7 @@ import { } from '@/modules/airtable'; import { sendCompanyReviewNotifications } from '@/modules/employment/use-cases/send-company-review-notifications'; import { syncLinkedInProfiles } from '@/modules/linkedin'; +import { batchRemoveMembers } from '@/modules/members/use-cases/batch-remove-members'; import { sendAnniversaryEmail } from '@/modules/members/use-cases/send-anniversary-email'; import { sendGraduationEmail } from '@/modules/members/use-cases/send-graduation-email'; import { success } from '@/shared/utils/core'; @@ -32,6 +33,9 @@ export const memberWorker = registerWorker( .with({ name: 'student.anniversary.email' }, ({ data }) => { return sendAnniversaryEmail(data); }) + .with({ name: 'student.batch_remove' }, ({ data }) => { + return batchRemoveMembers(data); + }) .with({ name: 'student.birthdate.daily' }, ({ data }) => { return sendBirthdayNotification(data); }) diff --git a/packages/core/src/modules/members/use-cases/batch-remove-members.ts b/packages/core/src/modules/members/use-cases/batch-remove-members.ts new file mode 100644 index 000000000..284f033cb --- /dev/null +++ b/packages/core/src/modules/members/use-cases/batch-remove-members.ts @@ -0,0 +1,24 @@ +import { db } from '@oyster/db'; + +import { job } from '@/infrastructure/bull'; +import { type GetBullJobData } from '@/infrastructure/bull.types'; + +export async function batchRemoveMembers({ + memberIds, +}: GetBullJobData<'student.batch_remove'>) { + const students = await db + .deleteFrom('students') + .where('id', 'in', memberIds) + .returning(['airtableId', 'email', 'firstName', 'slackId']) + .execute(); + + for (const student of students) { + job('student.removed', { + airtableId: student.airtableId as string, + email: student.email, + firstName: student.firstName, + sendViolationEmail: false, + slackId: student.slackId, + }); + } +}