feat(classes): server actions + page guard
classes.ts: class/session CRUD (classes.manage; delete refused once sessions/signups exist, archive instead), member signUpForClass / cancelMySignup as validated service-client writes enforcing eligibility, capacity vs waitlist, de-dupe and earliest-waitlist promotion on cancel, listUpcomingSessions (service-client counts so members see spots-left without seeing who signed up), instructor markAttendance and completeSession. completeSession awards a class's certification via the normal grantCertification path, so it only issues when the acting instructor also holds certifications.grant; otherwise completion still succeeds and reports certificatesSkipped. classes-guard mirrors the forms/certifications guards. Barrel + API_REFERENCE updated.
This commit is contained in:
@@ -909,3 +909,14 @@ Cert-type actions are gated by `certifications.manage`; grant actions by `certif
|
||||
- `renewCertification(input)` — granter. Resets `granted_at` to now and recomputes expiry from the cert's current validity; a revoked grant is terminal (issue a fresh grant instead).
|
||||
- `listMemberCertifications({ memberId })` — manager or granter; all grants for one member (joined with the cert) for the per-member award panel.
|
||||
- `getMyCertifications()` — the signed-in member's own grants. No params: the member is resolved from the session and RLS independently restricts a member to their own rows (the `/me` view).
|
||||
|
||||
### Classes: `classes.ts` (migration 032)
|
||||
Class/session management is gated by `classes.manage`; attendance/completion by `classes.instruct`. Member signup needs no permission (just membership). Gates use the `user_has_permission` RPC; the `classes`/`class_sessions`/`class_signups` RLS independently enforces the same.
|
||||
- `createClass` / `updateClass` / `deleteClass` / `listClasses` — `classes.manage`. `deleteClass` is refused once the class has sessions (archive via `updateClass {is_active:false}`). `payment_link` is a generic http(s) link only (no payment integration); `grants_certification_id` is validated to be a cert in the space.
|
||||
- `createSession` / `updateSession` / `deleteSession` — `classes.manage`. `updateSession` refuses `status:'completed'` (use `completeSession`); `deleteSession` refused if non-cancelled signups exist (cancel instead).
|
||||
- `listUpcomingSessions()` — any member; upcoming non-cancelled sessions of active classes, joined with the class. `registered_count` is computed with the service client so a member sees "spots left" without being able to read who else signed up (`class_signups` SELECT hides other members); `my_status` is the caller's own signup status.
|
||||
- `signUpForClass(input)` / `cancelMySignup(input)` — any member. Validated service-client writes (`class_signups` has no INSERT/DELETE policy): `signUpForClass` checks eligibility (`canSignUp`), de-dupes, and registers vs waitlists by effective capacity; `cancelMySignup` cancels (soft) and promotes the earliest waitlisted member when a registered seat frees (`pickPromotion`).
|
||||
- `listSessionSignups(input)` — manager or instructor; the attendee list for a session.
|
||||
- `markAttendance(input)` — `classes.instruct`; sets `attended` on a signup (RLS UPDATE = `classes.instruct`).
|
||||
- `completeSession(input)` — `classes.instruct`; marks the session completed (validated service-client transition, since the `class_sessions` UPDATE policy is `classes.manage`). If the class has `grants_certification_id`, it awards the cert to each attended member **through the normal `grantCertification` path**, so it only happens when the acting instructor also holds `certifications.grant`; otherwise completion still succeeds and the result reports `certificatesSkipped: true`.
|
||||
- `getMyClassSignups()` — the signed-in member's own signups (joined session + class). No params; RLS restricts to own rows (the `/me` view).
|
||||
|
||||
Reference in New Issue
Block a user