Context
Last sprint shipped draft save/load (getDraft, saveDraft, GET/POST /api/v1/registration). What's missing is the actual submit flow, server-side validation of responses, and enforcement of the registration-closed cutoff.
Tasks
Submit endpoint
Zod schema
Registration-closed enforcement
Auth gating
Frontend integration
Definition of done
Context
Last sprint shipped draft save/load (
getDraft,saveDraft, GET/POST/api/v1/registration). What's missing is the actual submit flow, server-side validation of responses, and enforcement of the registration-closed cutoff.Tasks
Submit endpoint
submit(userId, payload)insrc/lib/application/service.ts. Validates payload against the Zod schema, checksapplicationStatusisn't already'submitted', setsapplicationStatus: 'submitted', setsappSubmissionTime: new Date().PUT /api/v1/registrationinsrc/app/api/v1/registration/route.tsto callsubmit. Returns{ ok: true, submittedAt }on success, 400 with error details on validation failure, 403 if registration is closed, 409 if already submitted.Zod schema
src/lib/application/schema.tshas a Zod schema derived from the frozenquestions.tsquestion list. Every question has a rule matching its type: short-text is a non-empty string with max length, long-text has a max length, select has an enum of allowed options, multi-select is an array of allowed options, file-upload is a non-empty upload ID string.strictmode — no unknown keys allowed.Registration-closed enforcement
saveDraftandsubmit), read theregistration-closedsingleton via Ticket 5'sgetSingleton('registration-closed').registration-closed,saveDraftstill allows changes to already-submitted applications for now but blocks in-progress saves and new submits. Actually, simpler rule: pastregistration-closed, block both endpoints entirely with 403.registration-open: before now, reject with 403 and a "not yet open" error.Auth gating
requireUser()from Ticket 1 into all three methods (GET, POST, PUT) of/api/v1/registration. Replace the placeholderuserIdquery param withsession.user.id(or email, depending on how Ticket 1 keys users).userIdquery param handling and update the frontend to not send it.Frontend integration
PUT /api/v1/registration, gets the right responses and displays appropriate UI for each status code (400, 403, 409).Definition of done
applicationStatusto'submitted'in Mongo and recordsappSubmissionTime.registration-closedreturns 403.requireUser().