40227-vm/backend/docs/safety-quiz-results.md
2026-06-10 18:27:19 +02:00

4.2 KiB

Safety Quiz Results Backend

Purpose

safety_quiz_results stores weekly safety/de-escalation quiz submissions per authenticated staff user. The backend owns tenant scope, user ownership, the user display-name snapshot, the product role snapshot, and persistence. Each submission is an append (create) — there is no update path.

Slice Files (by layer)

  • Route: src/routes/safety_quiz_results.ts (thin wiring; GET /, POST /).
  • Controller: src/api/controllers/safety_quiz_results.controller.ts (custom — not the CRUD factory).
  • Service (BLL): src/services/safety_quiz_results.ts.
  • Repository (DAL): queries run through db.safety_quiz_results inside the service (no separate db/api/safety_quiz_results.ts).
  • Model: src/db/models/safety_quiz_results.ts.
  • Shared used: db/with-transaction.ts (withTransaction); shared/constants/pagination.ts (resolvePagination); services/shared/access.ts (getOrganizationIdOrGlobal, getCampusId, assertAuthenticatedTenantUser, hasRoleAccess, getDisplayName); shared/constants/roles.ts (GENERATED_ROLE_TO_PRODUCT_ROLE, PRODUCT_ROLE_VALUES); shared/constants/safety-quiz.ts (SAFETY_QUIZ_REPORT_ROLE_NAMES); shared/errors/validation.ts (ValidationError).

API

All routes require JWT authentication. Base path mounted at /api/safety_quiz_results.

  • GET /api/safety_quiz_results -> 200 { rows, count }. Optional query week_of, plus limit / page (paginated via resolvePagination). Returns results visible to the current user (see Access Rules), ordered by completed_at desc.
  • POST /api/safety_quiz_results -> 201. Request body wrapped as { data: <SafetyQuizInput> }. Returns the created result DTO.

Access Rules

  • All operations require an authenticated tenant user (assertAuthenticatedTenantUser).
  • create: a staff user creates a result for themselves; ownership fields are filled from the authenticated user.
  • list: users holding a role in SAFETY_QUIZ_REPORT_ROLE_NAMES (Super Administrator, Administrator, Platform Owner, Tenant Director, Campus Manager) or any role with globalAccess (via hasRoleAccess) see all org-level results; everyone else sees only their own rows (filtered by userId).

Tenant Scope

  • Organization is resolved via getOrganizationIdOrGlobal: global access users bypass the org filter and see results across all organizations; regular users are bound to their organization.
  • On create, campusId is set from getCampusId; userId, createdById, updatedById come from the current user.

Data Contract

  • Mutation input (SafetyQuizInput): quiz_id, quiz_title, week_of (non-empty strings); score and total_questions (integers); answers (an array of integers). Invalid input raises ValidationError.
  • On create the backend fills user_name from getDisplayName(currentUser) and user_role from the product-role mapping (GENERATED_ROLE_TO_PRODUCT_ROLE, defaulting to PRODUCT_ROLE_VALUES.TEACHER); completed_at is set to the current time. The frontend does not send name, role, or ownership fields.
  • DTO fields: id, quiz_id, quiz_title, week_of, score, total_questions, answers, user_name, user_role, completed_at, organizationId, campusId, userId, createdAt, updatedAt.
  • Model columns: quiz_id, quiz_title, week_of, user_name, user_role (all TEXT, not null); score, total_questions (INTEGER, not null); answers (JSONB, not null); completed_at (DATE, not null); importHash (unique); plus tenant/audit UUID columns (organizationId, campusId, userId, createdById, updatedById, all nullable). The model is paranoid (soft delete via deletedAt) and uses freezeTableName.
  • Associations: belongsTo organizations (organization), campuses (campus), users (user, createdBy, updatedBy).

Behavior / Notes

  • create runs inside withTransaction; trimmed string fields are persisted.
  • list is paginated with shared defaults (resolvePagination).

Tests

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

  • Frontend: frontend/docs/safety-quiz-integration.md.
  • Related slices: personality-quiz-results.md, walkthrough-checkins.md, user-progress.md.