4.6 KiB
4.6 KiB
Personality Quiz Results Backend
Purpose
personality_quiz_results stores each authenticated tenant user's current personality quiz
result (one row per user per organization) and exposes an aggregate distribution of personality
types for leadership reporting. The backend owns tenant scope, user ownership, the saved
personality type, and the answer snapshot. It does not write to staff profile records.
Slice Files (by layer)
- Route:
src/routes/personality_quiz_results.ts(thin wiring;GET /me,PUT /me,GET /distribution). - Controller:
src/api/controllers/personality_quiz_results.controller.ts(custom — not the CRUD factory). - Service (BLL):
src/services/personality_quiz_results.ts. - Repository (DAL): queries run through
db.personality_quiz_resultsinside the service (no separatedb/api/personality_quiz_results.ts). - Model:
src/db/models/personality_quiz_results.ts. - Shared used:
db/with-transaction.ts(withTransaction);services/shared/access.ts(getOrganizationIdOrGlobal,getCampusId,assertAuthenticatedTenantUser,hasRoleAccess);shared/constants/personality.ts(PERSONALITY_REPORT_ROLE_NAMES);shared/errors/*(ForbiddenError,ValidationError).
API
All routes require JWT authentication. Base path mounted at /api/personality_quiz_results.
GET /api/personality_quiz_results/me->200. Returns the current user's saved result DTO (most recently updated), ornullif none exists.PUT /api/personality_quiz_results/me->200. Request body wrapped as{ data: { personality_type, quiz_answers } }. Creates or updates the current user's result and returns the saved DTO.GET /api/personality_quiz_results/distribution->200. Optional querycampusId. Returns{ rows: [{ type, count }], count }(number of distinct personality types). Restricted to report roles.
Access Rules
getCurrentUserResult/upsertCurrentUserResult: any authenticated tenant user (assertAuthenticatedTenantUser); each user reads and writes only their own result (filtered byuserId).distribution: restricted toPERSONALITY_REPORT_ROLE_NAMES(super_admin,system_admin,owner,superintendent,director) or any role withglobalAccess, enforced byhasRoleAccess; otherwiseForbiddenError. The distribution response contains onlytypeandcountper group — no individual names or answers.
Tenant Scope
- Organization is resolved via
getOrganizationIdOrGlobal: global access users bypass the org filter and can see their results across organizations; regular users are bound to their org. - On upsert,
campusIdis set fromgetCampusId(the current staff profile's campus, else the user'scampusId, elsenull);userId,createdById,updatedByIdcome from the current user. distributionfilters by organization viagetOrganizationIdOrGlobal(global users see all orgs) and, when acampusIdquery value is provided, additionally by that campus.
Data Contract
- Mutation input (
PUT /me):personality_type(non-empty string) andquiz_answers(a non-array object whose values are all non-empty strings). Invalid input raisesValidationError. - On save,
personality_typeis trimmed and upper-cased;completed_atis set to the current time. - DTO fields:
id,personality_type,quiz_answers,completed_at,organizationId,campusId,userId,createdById,updatedById,createdAt,updatedAt. - Model columns:
personality_type(TEXT, not null),quiz_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 isparanoid(soft delete viadeletedAt) and usesfreezeTableName. - Associations:
belongsToorganizations (organization), campuses (campus), users (user,createdBy,updatedBy).
Behavior / Notes
upsertCurrentUserResultruns insidewithTransaction: it looks up the existing row byorganizationId+userIdand updates it, otherwise creates a new one.getCurrentUserResultorders byupdatedAtdesc and returns the first match.distributiongroups bypersonality_typewith aCOUNT(id)aggregate, ordered by count desc;countin the response is the number of distinct types returned.
Tests
None yet (no personality_quiz_results unit/e2e test in src/).
Related
- Frontend:
frontend/docs/personality-integration.md,frontend/docs/personality-catalog.md. - Related slices:
safety-quiz-results.md,walkthrough-checkins.md,user-progress.md(similar per-user tenant-scoped result/progress pattern).