Goal
GET /api/v1/status returns the correct dashboard branch for any user state. The state machine is fully implemented and exhaustively tested.
Tasks
Define the state machine inputs and outputs
Implement src/lib/status/machine.ts
Implement getApplicantStatus(userId) in src/lib/status/service.ts
Wire the API endpoint
Exhaustive unit tests
Integration test
Definition of done
Goal
GET /api/v1/statusreturns the correct dashboard branch for any user state. The state machine is fully implemented and exhaustively tested.Tasks
Define the state machine inputs and outputs
src/lib/status/types.ts:DashboardBranchis the union'pre-registration' | 'in-progress' | 'submitted' | 'admitted' | 'waitlisted' | 'declined'.ApplicantStatusis'not-started' | 'in-progress' | 'submitted'.DecisionStatusis'undecided' | 'admitted' | 'waitlisted' | 'declined'.{ user: { applicationStatus, decisionStatus, rsvpStatus }, dates: { registrationOpen, registrationClosed, confirmBy }, showDecision: boolean, now: Date }.Implement
src/lib/status/machine.tsdecideBranch(input): returns the correctDashboardBranch.now < registrationOpen, return'pre-registration'. Else ifapplicationStatus !== 'submitted', return'in-progress'. Else ifshowDecision === falseORdecisionStatus === 'undecided', return'submitted'. Else returndecisionStatus('admitted' | 'waitlisted' | 'declined').applicationStatus === 'not-started'andnow > registrationOpen? Return'in-progress'(the dashboard shows "continue your application" which works fine for not-yet-started users too). What ifdecisionStatus === 'admitted'butrsvpStatus === 'declined'? Still return'admitted'(the dashboard view itself handles the RSVP state).Implement
getApplicantStatus(userId)insrc/lib/status/service.tsapplicant_datausing the Mongo client.src/lib/status/mock-singletons.tsmock file. Add a comment// TODO: replace with getSingleton() calls once Ticket 5 lands real singleton service.showDecisionthe same way.decideBranch, return{ branch, user, dates, showDecision }.Wire the API endpoint
src/app/api/v1/status/route.ts. ImplementGET: readuserIdfrom query string (placeholder until auth ships), callgetApplicantStatus, return JSON.// TODO: gate with requireUser() once Ticket 1 ships its helpers.Exhaustive unit tests
src/lib/status/machine.test.ts.now < registrationOpenalways returns'pre-registration', regardless of other fields.applicationStatus === 'in-progress'andnow >= registrationOpenreturns'in-progress'.applicationStatus === 'not-started'andnow >= registrationOpenreturns'in-progress'.applicationStatus === 'submitted',decisionStatus === 'admitted',showDecision === falsereturns'submitted'.applicationStatus === 'submitted',decisionStatus === 'undecided',showDecision === truereturns'submitted'.applicationStatus === 'submitted',decisionStatus === 'admitted',showDecision === truereturns'admitted'.'waitlisted'.'declined'.Integration test
Definition of done
GET /api/v1/status?userId=Xreturns the right branch for X based on Mongo state.