feat: selectFinalists creates PENDING confirmations and sends emails
- New service module createPendingConfirmation: writes a PENDING FinalistConfirmation row with a signed token whose exp matches the computed deadline. - selectFinalists admin mutation: reads windowHours from the round's configJson.confirmationWindowHours (default 24), validates category match + quota, then creates one confirmation per selected project and sends a notification email to the team lead. Email failures are logged but never roll back the row creation. - New email helpers: getFinalistConfirmationTemplate + sendFinalistConfirmationEmail.
This commit is contained in:
40
src/server/services/finalist-confirmation.ts
Normal file
40
src/server/services/finalist-confirmation.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { CompetitionCategory, PrismaClient } from '@prisma/client'
|
||||
import { signFinalistToken } from '@/lib/finalist-token'
|
||||
|
||||
type AnyPrisma = Pick<PrismaClient, 'finalistConfirmation'>
|
||||
|
||||
/**
|
||||
* Create a PENDING FinalistConfirmation row with a signed token. Caller is
|
||||
* responsible for sending the notification email separately.
|
||||
*/
|
||||
export async function createPendingConfirmation(
|
||||
prisma: AnyPrisma,
|
||||
args: {
|
||||
projectId: string
|
||||
category: CompetitionCategory
|
||||
windowHours: number
|
||||
promotedFromWaitlistEntryId?: string
|
||||
},
|
||||
): Promise<{ id: string; token: string; deadline: Date }> {
|
||||
const deadline = new Date(Date.now() + args.windowHours * 3_600_000)
|
||||
// Generate the row ID up front so we can sign it into the token before
|
||||
// writing the row (token is unique-indexed; embedding the ID gives the
|
||||
// public verify path a stable lookup key).
|
||||
const id = `cmfc_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`
|
||||
const token = signFinalistToken({
|
||||
confirmationId: id,
|
||||
exp: Math.floor(deadline.getTime() / 1000),
|
||||
})
|
||||
await prisma.finalistConfirmation.create({
|
||||
data: {
|
||||
id,
|
||||
projectId: args.projectId,
|
||||
category: args.category,
|
||||
status: 'PENDING',
|
||||
deadline,
|
||||
token,
|
||||
promotedFromWaitlistEntryId: args.promotedFromWaitlistEntryId ?? null,
|
||||
},
|
||||
})
|
||||
return { id, token, deadline }
|
||||
}
|
||||
Reference in New Issue
Block a user