87 lines
4.5 KiB
Markdown
87 lines
4.5 KiB
Markdown
# User Progress Backend
|
|
|
|
## Purpose
|
|
|
|
`user_progress` stores per-user progress for narrow staff workflows, keyed by a typed
|
|
`progress_type` and an `item_id`. Current supported types are `sign_learned` (sign-language items
|
|
learned) and `zone_checkin` (zones-of-regulation check-ins). The backend owns tenant scope, user
|
|
ownership, validation, and persistence (one row per user + type + item, upserted).
|
|
|
|
## Slice Files (by layer)
|
|
|
|
- Route: `src/routes/user_progress.ts` (thin wiring; `GET /`, `POST /`, `DELETE /by-item`).
|
|
- Controller: `src/api/controllers/user_progress.controller.ts` (custom — not the CRUD factory).
|
|
- Service (BLL): `src/services/user_progress.ts`.
|
|
- Repository (DAL): queries run through `db.user_progress` inside the service (no separate
|
|
`db/api/user_progress.ts`).
|
|
- Model: `src/db/models/user_progress.ts`.
|
|
- Shared used: `db/with-transaction.ts` (`withTransaction`); `services/shared/access.ts`
|
|
(`assertAuthenticatedTenantUser`, `getCampusId`, `getOrganizationIdOrGlobal`, `requireUserId`);
|
|
`shared/constants/user-progress.ts` (`USER_PROGRESS_TYPE_VALUES`, `UserProgressType`);
|
|
`shared/constants/pagination.ts` (`resolvePagination`); `shared/errors/validation.ts`
|
|
(`ValidationError`).
|
|
|
|
## API
|
|
|
|
All routes require JWT authentication. Base path mounted at `/api/user_progress`.
|
|
|
|
- `GET /api/user_progress` -> `200` `{ rows, count }`. Required query `progress_type` (must be a
|
|
valid type); optional `item_id`, plus `limit` / `page` (paginated via `resolvePagination`).
|
|
Returns the current user's rows for that type, ordered by `createdAt` desc.
|
|
- `POST /api/user_progress` -> `200`. Request body wrapped as `{ data: <UserProgressInput> }`.
|
|
Creates or updates one progress item and returns the saved DTO.
|
|
- `DELETE /api/user_progress/by-item` -> `200` `{ deletedCount }`. Required query `progress_type`
|
|
and `item_id`. Deletes the current user's row for that type + item.
|
|
|
|
## Access Rules
|
|
|
|
- All operations require an authenticated tenant user (`assertAuthenticatedTenantUser`).
|
|
- No additional role gating: every operation is bound to the current user. List, upsert, and delete
|
|
are all filtered by `userId` (`requireUserId`), so a user only ever reads, writes, or deletes
|
|
their own progress. Frontend-provided names or roles are not trusted for ownership.
|
|
|
|
## Tenant Scope
|
|
|
|
- Organization is resolved via `getOrganizationIdOrGlobal`: global access users bypass the org
|
|
filter; regular users are bound to their organization. All queries are still filtered by
|
|
`userId` so each user only sees their own progress.
|
|
- `userId` is required from the current user (`requireUserId`).
|
|
- On upsert, `campusId` is set from `getCampusId`; `updatedById` from the current user, and
|
|
`createdById` on create.
|
|
|
|
## Data Contract
|
|
|
|
- Mutation input (`UserProgressInput`): `progress_type` (must be one of
|
|
`USER_PROGRESS_TYPE_VALUES`: `sign_learned`, `zone_checkin`) and `item_id` (non-empty string) are
|
|
required. Optional: `value`, `score`, `metadata`. Invalid input raises `ValidationError`.
|
|
- On save, `value` is persisted only if a string (else `null`), `score` only if a number (else
|
|
`null`), and `metadata` defaults to `null` when absent; `item_id` is trimmed.
|
|
- DTO fields: `id`, `progress_type`, `item_id`, `value`, `score`, `metadata`, `organizationId`,
|
|
`campusId`, `userId`, `createdAt`, `updatedAt`.
|
|
- Model columns: `progress_type` (ENUM over `USER_PROGRESS_TYPE_VALUES`, not null), `item_id`
|
|
(TEXT, not null), `value` (TEXT, nullable), `score` (INTEGER, nullable), `metadata` (JSONB,
|
|
nullable), `importHash` (unique). Tenant/audit columns: `organizationId` (not null), `userId`
|
|
(not null), `createdById` (not null), `campusId` (nullable), `updatedById` (nullable). The model
|
|
is `paranoid` (soft delete via `deletedAt`) and uses `freezeTableName`.
|
|
- Associations: `belongsTo` organizations (`organization`), campuses (`campus`), users (`user`,
|
|
`createdBy`, `updatedBy`).
|
|
|
|
## Behavior / Notes
|
|
|
|
- `upsert` runs inside `withTransaction`: it looks up the existing row by `organizationId` +
|
|
`userId` + `progress_type` + `item_id`, updating it if present, otherwise creating it.
|
|
- `list` validates `progress_type` and is paginated with shared defaults.
|
|
- `removeByItem` is a hard `destroy` call (no transaction wrapper) returning the number of rows
|
|
deleted.
|
|
|
|
## Tests
|
|
|
|
None yet (no `user_progress` unit/e2e test in `src/`).
|
|
|
|
## Related
|
|
|
|
- Frontend: `frontend/docs/user-progress-integration.md`,
|
|
`frontend/docs/sign-language-integration.md`, `frontend/docs/zones-of-regulation-integration.md`.
|
|
- Related slices: `safety-quiz-results.md`, `personality-quiz-results.md`,
|
|
`walkthrough-checkins.md`.
|