# 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: `campusId`. Missing/invalid input
raises `ValidationError`.

## Behavior / Notes

- Create/update run inside `withTransaction`.
- List is paginated with the shared defaults (`resolvePagination`).

## Tests

None yet (no `frame_entries` unit/e2e test in `src/`).

## Related

- Frontend: `frontend/docs/frame-integration.md`.
- Related slices: `user-progress.md` (dashboard zone check-ins), `staff` (campus resolution).
