Skip to content

Dead route + lost validation: PATCH /api/programs/[id]/settings is never called #477

Description

@thpr

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/.../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 maxParticipants below 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

  1. 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.
  2. 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
  • Actually-used path: checkin-app/src/app/api/programs/[id]/route.ts (PATCH)
  • UI: checkin-app/src/app/program-ops/programs/[id]/page.tsx (handleSaveGeneral)
  • Enforcement that the missing checks protect: checkin-app/src/lib/program/capacity.ts, POST /api/programs/[id]/participants, POST /api/programs/[id]/public-register
  • Related: Dead route + lost validation: POST /api/programs/[id]/publish is never called #476 (dead publish route), dead POST /api/programs/[id]/events

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions