You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PATCH /api/programs/[id]/settings (checkin-app/src/app/api/programs/[id]/settings/route.ts) has no client consumer. A grep for /api/programs/.../settings across checkin-app/src finds only the route itself and its integration test. No UI page or component fetches it.
How program settings are actually saved
The program edit form at checkin-app/src/app/program-ops/programs/[id]/page.tsx (handleSaveGeneral) sends every settings field — name, begin, end, phase, enrollmentStatus, memberOnly, minAge, maxAge, maxParticipants, leadMentorId, memberPrice, nonMemberPrice — through the generic PATCH /api/programs/[id] (checkin-app/src/app/api/programs/[id]/route.ts). The dedicated settings handler is a near-duplicate that is bypassed entirely.
Why this matters (not just dead code)
The settings handler carries validation that the generic PATCH does not:
Age sanity:minAge / maxAge must be non-negative, and effective minAge <= maxAge (uses body-overrides-current so a one-sided edit can't invert the range).
Capacity floor:maxParticipants must be a positive integer and not below the current enrollment count — otherwise the capacity lock perma-rejects every future enroller.
The base PATCH /api/programs/[id] performs none of these checks. It writes minAge, maxAge, and maxParticipants straight through. So today via the UI a lead/admin can:
Set minAge > maxAge, making the age gate reject everyone (age >= minAge && age <= maxAge is unsatisfiable). Both self/household enroll (POST /api/programs/[id]/participants) and public register (POST /api/programs/[id]/public-register) enforce that range.
Set maxParticipantsbelow the number already enrolled. The capacity check (lockProgramAndCheckCapacity) then rejects all new enrollments even though spots were intended — and existing over-cap enrollees are silently stranded.
Set negative ages (no < 0 guard on the base path).
Comparison
check
settings (dead)
base [id] PATCH (used)
minAge/maxAge >= 0
yes
no
effective minAge <= maxAge
yes
no
maxParticipants positive int
yes
no
maxParticipants >= current enrollment
yes
no
writes price fields
no
yes
Note the base PATCH also handles memberPrice/nonMemberPrice, which settings does not — so the two handlers aren't a clean superset either; they diverged.
Options
Consolidate (recommended): fold the settings validation (age sanity + capacity floor + non-negative ages) into the single PATCH /api/programs/[id] the UI actually calls, then delete the settings/ route. One handler, all checks. Pairs naturally with the publish-route finding (Dead route + lost validation: POST /api/programs/[id]/publish is never called #476) and the dead [id]/events route.
Wire the UI to settings and add the missing price handling there, then make [id] PATCH narrower. More churn, no clear benefit.
References
Dead route: checkin-app/src/app/api/programs/[id]/settings/route.ts
Enforcement that the missing checks protect: checkin-app/src/lib/program/capacity.ts, POST /api/programs/[id]/participants, POST /api/programs/[id]/public-register
Summary
PATCH /api/programs/[id]/settings(checkin-app/src/app/api/programs/[id]/settings/route.ts) has no client consumer. A grep for/api/programs/.../settingsacrosscheckin-app/srcfinds only the route itself and its integration test. No UI page or component fetches it.How program settings are actually saved
The program edit form at
checkin-app/src/app/program-ops/programs/[id]/page.tsx(handleSaveGeneral) sends every settings field —name,begin,end,phase,enrollmentStatus,memberOnly,minAge,maxAge,maxParticipants,leadMentorId,memberPrice,nonMemberPrice— through the genericPATCH /api/programs/[id](checkin-app/src/app/api/programs/[id]/route.ts). The dedicatedsettingshandler is a near-duplicate that is bypassed entirely.Why this matters (not just dead code)
The
settingshandler carries validation that the generic PATCH does not:minAge/maxAgemust be non-negative, and effectiveminAge <= maxAge(uses body-overrides-current so a one-sided edit can't invert the range).maxParticipantsmust be a positive integer and not below the current enrollment count — otherwise the capacity lock perma-rejects every future enroller.The base
PATCH /api/programs/[id]performs none of these checks. It writesminAge,maxAge, andmaxParticipantsstraight through. So today via the UI a lead/admin can:minAge > maxAge, making the age gate reject everyone (age >= minAge && age <= maxAgeis unsatisfiable). Both self/household enroll (POST /api/programs/[id]/participants) and public register (POST /api/programs/[id]/public-register) enforce that range.maxParticipantsbelow the number already enrolled. The capacity check (lockProgramAndCheckCapacity) then rejects all new enrollments even though spots were intended — and existing over-cap enrollees are silently stranded.< 0guard on the base path).Comparison
settings(dead)[id]PATCH (used)minAge/maxAge>= 0minAge <= maxAgemaxParticipantspositive intmaxParticipants>= current enrollmentNote the base PATCH also handles
memberPrice/nonMemberPrice, whichsettingsdoes not — so the two handlers aren't a clean superset either; they diverged.Options
settingsvalidation (age sanity + capacity floor + non-negative ages) into the singlePATCH /api/programs/[id]the UI actually calls, then delete thesettings/route. One handler, all checks. Pairs naturally with the publish-route finding (Dead route + lost validation: POST /api/programs/[id]/publish is never called #476) and the dead[id]/eventsroute.settingsand add the missing price handling there, then make[id]PATCH narrower. More churn, no clear benefit.References
checkin-app/src/app/api/programs/[id]/settings/route.tscheckin-app/src/app/api/programs/[id]/route.ts(PATCH)checkin-app/src/app/program-ops/programs/[id]/page.tsx(handleSaveGeneral)checkin-app/src/lib/program/capacity.ts,POST /api/programs/[id]/participants,POST /api/programs/[id]/public-registerPOST /api/programs/[id]/events