S4-5: new routers/exam/ package mounted at /api/exam (R5.1/E5, not under
/database/). Template CRUD with hybrid persistence (R5.2):
- POST/GET/GET{id}/PUT{id}/DELETE{id} /templates + PATCH /questions/{qid}
- Calls Supabase AS THE USER via SupabaseAnonClient.for_user (E1 fix), so the
RLS in 72-exam-marker.sql is enforced; no service-role for user-facing ops.
- Institute resolved/validated via the user_institute_ids() SECURITY DEFINER
RPC (institute_memberships is deny-all as-user per E4); client-supplied
institute_id is validated, never trusted (R5.5).
- Ownership pre-checked before writes (E2); out-of-scope ids read back as 404
under RLS (IDOR-safe). Soft-delete archives, never hard-deletes.
- PUT full-replace preserves client UUIDs as Neo4j join keys (spec §2).
- eb_exams.exam_code denormalised via a documented service-role catalogue
lookup (eb_exams is shared reference data, deny-all as-user per E4).
Unit tests cover auth, CRUD, ownership/IDOR, institute validation, soft-delete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 lines
341 B
Python
10 lines
341 B
Python
"""Exam-marker API package (/api/exam/).
|
|
|
|
A clean top-level router group (R5.1/E5), deliberately NOT nested under /database/. Every
|
|
endpoint authenticates the JWT and calls Supabase as-the-user so the RLS in
|
|
volumes/db/cc/72-exam-marker.sql is enforced (spec E1/E2 fixes).
|
|
"""
|
|
from routers.exam.templates import router
|
|
|
|
__all__ = ["router"]
|