40227-vm/backend/docs/frame-entries.md
2026-06-12 10:56:13 +02:00

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_entries inside the service (no separate db/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 via resolvePagination).
  • POST /api/frame_entries -> 201 the created entry DTO.
  • PUT /api/frame_entries/:id -> 200 the 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 by assertCanEdit via hasRoleAccess; a non-editor gets ForbiddenError. Frontend may hide editing controls, but the backend check is authoritative.

Tenant Scope

  • Organization is resolved via getOrganizationIdOrGlobal: users with globalAccess bypass the org filter and see/create entries across all organizations; regular users are bound to their organization.
  • campusId is optional; when omitted it defaults to the current staff profile's campus (currentUser.staff_user[0].campusId) when available, else null.

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-start week_of normalization (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 persisted week_of is normalized to its Sunday week-start.
  • Frontend: frontend/docs/frame-integration.md.
  • Related slices: user-progress.md (dashboard zone check-ins), staff (campus resolution).