permits: admin CRUD (route + UI)
A permit is an aggregate (row + credentials + bound plates); create/update treat it as one unit (child sets replaced on update). GET /api/permits (any signed-in role, for lookup); POST/PUT/DELETE + POST /:id/revoke (admin only). Validation: maxConcurrent positive-int-or-null (unbound); a permit must have at least one credential OR one bound plate. Revoke is the soft common case (keeps history, barred at the barrier); DELETE hard-removes — past ledger events that reference it are untouched (append-only audit trail, independent of this row). Web PermitManager in the admin shell: list + add/edit (holder, car-bound toggle, validity, credentials, plates), revoke, delete. Makes permits usable without hand-seeding (companion to the tariff composer). Verified via inject: validation (empty / maxConcurrent=0 -> 400), create -> 201, operator can LIST but not write (403), update replaces child rows, revoke -> revoked, delete -> 204 then 404 with children cleaned.
This commit is contained in:
@@ -119,6 +119,16 @@ serves both populations ([[entry-exit-readers]]), disambiguated by *what the cre
|
||||
- Refusals (revoked / out-of-window / at-capacity) are signed `anomaly` events; the barrier stays
|
||||
closed. Verified end to end (entry, inferred exit, fleet cap, plate-bound, revoked, dispatch).
|
||||
|
||||
**Admin CRUD** (`apps/server/src/routes/permits.ts` + `apps/web/src/PermitManager.tsx`): a permit is
|
||||
an **aggregate** (the row + its credentials + bound plates); create/update treat it as one unit
|
||||
(child sets are replaced on update). `GET /api/permits` (any signed-in role — for lookup),
|
||||
`POST/PUT/DELETE /api/permits[/:id]` + `POST /api/permits/:id/revoke` (**admin only**). Validation:
|
||||
`maxConcurrent` is a positive int or `null` (unbound); a permit must have **at least one credential
|
||||
or one bound plate** (else nothing identifies it). Revoke is the soft, common case (keeps history,
|
||||
barred at the barrier); DELETE hard-removes — past ledger events that reference the permit are
|
||||
untouched (the audit trail is append-only and independent). Verified via inject (validation, child
|
||||
replacement, RBAC, revoke/delete).
|
||||
|
||||
## Resolved (2026-06-15)
|
||||
|
||||
- **Two optional bindings, independent:** car-count (`maxConcurrent`, **default 1**, raisable or
|
||||
|
||||
+13
@@ -564,3 +564,16 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section).
|
||||
F1 exits → F3 enters); plate-bound permit opens; revoked → reject; unknown credential falls through
|
||||
to exit-flow reject (not mis-read as permit); verifyChain ok. Full build 5/5.
|
||||
- Updated [[permit]] (as-built), [[parking-session]] (read dispatch).
|
||||
|
||||
## [2026-06-15] build | Permit admin CRUD (route + UI)
|
||||
- `apps/server/src/routes/permits.ts`: a permit is an aggregate (row + credentials + bound plates);
|
||||
create/update replace the child sets as one unit. GET (any role, for lookup), POST/PUT/DELETE +
|
||||
POST /:id/revoke (admin only). Validation: maxConcurrent positive-int-or-null; must have ≥1
|
||||
credential OR ≥1 plate. Revoke = soft (keeps history); DELETE = hard (past ledger events untouched).
|
||||
- `apps/web/src/PermitManager.tsx` in the admin shell: list + add/edit (holder, car-bound toggle →
|
||||
maxConcurrent or unbound, validity window, credentials add/remove, plates as a list), revoke, delete.
|
||||
- Makes permits usable without hand-seeding (companion to the tariff composer).
|
||||
- VERIFIED via inject: empty + maxConcurrent=0 → 400 w/ messages; valid → 201; operator LIST 200 but
|
||||
create 403; update unbinds + REPLACES child rows (old cred gone); revoke→revoked; delete→204 then
|
||||
404, children cleaned. Full build 5/5.
|
||||
- Updated [[permit]] (CRUD as-built).
|
||||
|
||||
Reference in New Issue
Block a user