26 lines
1.7 KiB
TypeScript
26 lines
1.7 KiB
TypeScript
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 };
|
|
});
|
|
}
|