3.6 KiB
FRAME Entries Backend
Purpose
frame_entries stores weekly F.R.A.M.E. focus entries per organization. The backend is the
source of truth for persisted FRAME data; the frontend never substitutes static samples for it.
Slice Files (by layer)
- Route:
src/routes/frame_entries.ts(thin wiring;GET /,POST /,PUT /:id). - Controller:
src/api/controllers/frame_entries.controller.ts(custom — not the CRUD factory). - Service (BLL):
src/services/frame_entries.ts. - Repository (DAL): queries run through
db.frame_entriesinside the service (no separatedb/api/frame_entries.ts). - Model:
src/db/models/frame_entries.ts. - Shared used:
db/with-transaction.ts(withTransaction),services/shared/access.ts(getOrganizationIdOrGlobal,hasRoleAccess),shared/constants/pagination.ts(resolvePagination),shared/constants/frame.ts(FRAME_EDITOR_ROLE_NAMES),shared/errors/*(ForbiddenError,ValidationError).
API
All routes require JWT authentication.
GET /api/frame_entries->200{ rows, count }for the current user's organization (paginated viaresolvePagination).POST /api/frame_entries->201the created entry DTO.PUT /api/frame_entries/:id->200the updated entry DTO (scoped to the org).
Request body for create/update is wrapped as { data: <FrameEntryInput> }.
Access Rules
- Read: any authenticated user in the organization, or any user with
globalAccess(sees all organizations). - Edit (create/update): restricted to roles in
FRAME_EDITOR_ROLE_NAMES(director/superintendent capabilities) —super_admin,system_admin,owner,superintendent,director. Enforced byassertCanEditviahasRoleAccess; a non-editor getsForbiddenError. Frontend may hide editing controls, but the backend check is authoritative.
Tenant Scope
- Organization is resolved via
getOrganizationIdOrGlobal: users withglobalAccessbypass the org filter and see/create entries across all organizations; regular users are bound to their organization. campusIdis optional; when omitted it defaults to the current staff profile's campus (currentUser.staff_user[0].campusId) when available, elsenull.
Data Contract
Required request fields (REQUIRED_FIELDS): week_of, posted_date, formal, recognition,
application, management, emotional, author. Optional: week_label, campusId.
Missing/invalid input raises ValidationError.
week_of is the week the entry covers, sent as a YYYY-MM-DD date and stored as the
canonical Sunday-start ISO date (American week) — the server normalizes it via
toWeekStartIso (shared/constants/week.ts) and rejects non-dates. week_label is an optional
free-text note for that week (e.g. "Spring Break week"), stored trimmed or null.
Behavior / Notes
- Create/update run inside
withTransaction. - List is paginated with the shared defaults (
resolvePagination). - The same Sunday-start canonicalization is used on the frontend
(
shared/business/week.ts) for the dashboard hero, the safety-quiz week, and the F.R.A.M.E. week picker, so the week is consistent everywhere.
Tests
- Unit (
npm test):shared/constants/week.test.ts— the Sunday-startweek_ofnormalization (toWeekStartIso) + invalid-date rejection. - Seeded e2e (
frontend/tests/e2e/product-workflow.seeded.e2e.ts,npm run test:e2e:content): a director posts a FRAME entry and reads it back, asserting the persistedweek_ofis normalized to its Sunday week-start.
Related
- Frontend:
frontend/docs/frame-integration.md. - Related slices:
user-progress.md(dashboard zone check-ins),staff(campus resolution).