import { and, eq, sql } from "drizzle-orm"; import { emailDeliveries, getDb, guests } from "@album/database"; import { renderAlbumReadyEmail } from "@album/email"; import { writeAudit } from "./audit"; import { publicAppOrigin } from "./public-app-url"; export async function notifyEventGuests(event: { id: string; groupId: string; slug: string; title: string }, actorUserId: string) { return getDb().transaction(async (tx) => { const rows = await tx.select().from(guests).where(eq(guests.eventId, event.id)); const previous = new Set(rows.filter((g) => g.notifiedAt || g.notificationClaimedAt).flatMap((g) => g.email ? [g.email.toLowerCase()] : [])); const recipients = new Set(rows.filter((g) => g.notifyWhenReady && g.email && !previous.has(g.email.toLowerCase())).map((g) => g.email!.toLowerCase())); let queued = 0; for (const recipient of recipients) { const inserted = await tx.insert(emailDeliveries).values({ eventId: event.id, recipient, provider: process.env.EMAIL_PROVIDER ?? "resend", payload: renderAlbumReadyEmail({ to: recipient, eventTitle: event.title, galleryUrl: `${publicAppOrigin()}/e/${event.slug}` }), }).onConflictDoNothing().returning({ id: emailDeliveries.id }); if (!inserted.length) continue; await tx.update(guests).set({ notificationClaimedAt: new Date() }).where(and(eq(guests.eventId, event.id), eq(guests.notifyWhenReady, true), sql`lower(${guests.email}) = ${recipient}`)); queued++; } await writeAudit({ eventId: event.id, groupId: event.groupId, actorUserId, action: "guest.email.queued", subjectType: "event", subjectId: event.id, metadata: { queued } }, tx); return { queued }; }); }