7.4 KiB
7.4 KiB
Personality Quiz Results Backend
Purpose
personality_quiz_results stores authenticated tenant users' Emotional Intelligence and
Personality quiz completion history. The backend owns tenant scope, user ownership, quiz kind,
quiz version identifiers, weekly EI completion windows, personality type snapshots, scores, and
answer snapshots. It does not write to user employment fields.
Slice Files (by layer)
- Route:
src/routes/personality_quiz_results.ts(thin wiring;GET /me,GET /me/history,PUT /me,GET /distribution,GET /completion). - 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,hasFeaturePermission);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. Queryquiz_kindcan beei_self_assessmentorpersonality_type. Returns the current user's latest saved result DTO for that kind, ornullif none exists. EI self-assessment reads are limited to the current Sunday-start week. Personality type reads use the user's latest historical result.GET /api/personality_quiz_results/me/history->200. Optional queryquiz_kindcan beei_self_assessmentorpersonality_type; optionallimitdefaults to 25 and is capped at 100. Returns{ rows, count }for the current user's saved EI and personality quiz history ordered bycompleted_atdescending. This endpoint is used by the profile page so historical completions remain visible after weekly EI windows roll forward.PUT /api/personality_quiz_results/me->200. Request body wrapped as{ data: { quiz_kind, quiz_id, quiz_title, quiz_answers, total_questions, ... } }. Creates a new completion-history row and returns the saved DTO. If the caller is a parent-scope user acting through a drilled child scope, the request is accepted as a no-op and returnsnull.GET /api/personality_quiz_results/distribution->200. Optional querycampusId. Returns{ rows: [{ type, count }], count }(number of distinct personality types). Restricted to report roles.GET /api/personality_quiz_results/completion->200. Optional queryquiz_kind. Returns staff completion rows with current-week EI self-assessment status and latest personality type status. Restricted toREAD_PERSONALITY_REPORTS.
Access Rules
getCurrentUserResult/getCurrentUserHistory/upsertCurrentUserResult: any authenticated tenant user (assertAuthenticatedTenantUser); each user reads and writes only their own result (filtered byuserId).upsertCurrentUserResultpersists only when the active scope is the user's own scope. Parent users drilled into a child school/campus/classroom can complete the UI flow there, but the backend does not create or update reportable quiz rows for that child scope.distributionandcompletion: restricted toREAD_PERSONALITY_REPORTS; otherwiseForbiddenError. Role-seeded permissions are only the baseline grants. The distribution response contains onlytypeandcountper group — no individual names or answers.custom_permissionscan grant the report permission andcustom_permissions_filtercan remove it for non-global users.
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 user's direct campus, else the user'scampusId, elsenull);userId,createdById,updatedByIdcome from the current user. - Drilled child scopes are not treated as the user's own scope for personal saves, even though reads and reports use the active scope for visibility.
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):quiz_kind,quiz_id,quiz_title,quiz_answers, andtotal_questions. Personality type submissions also requirepersonality_type; EI submissions may includescore,result_label, andresult_payload. Invalid input raisesValidationError. - On save, personality types are trimmed and upper-cased;
completed_atis set to the current time. EI self-assessment rows getweek_ofset to the current Sunday-start week. Personality type rows keepweek_of = nullbecause the type quiz is not a weekly workflow. - DTO fields:
id,quiz_kind,quiz_id,quiz_title,personality_type,quiz_answers,score,total_questions,result_label,result_payload,week_of,user_name,user_role,completed_at,organizationId,campusId,userId,createdById,updatedById,createdAt,updatedAt. - Model columns:
quiz_kind,quiz_id,quiz_title,personality_type,quiz_answers,score,total_questions,result_label,result_payload,week_of,user_name,user_role,completed_at,importHash, 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
upsertCurrentUserResultfirst checks whether the caller is acting in their own scope. If not, it skips persistence and returnsnull. Otherwise it creates a new history row insidewithTransaction; old completions are never overwritten.getCurrentUserResultorders bycompleted_atdesc and returns the latest match. Forei_self_assessment, the query includes currentweek_of; forpersonality_type, it does not.getCurrentUserHistoryreturns the user's saved EI and personality completions across weeks and quiz versions. It never exposes another user's rows and does not require report permissions.distributionkeeps each user's latest personality type result, counts those current types, and orders groups by count desc;countin the response is the number of distinct types returned.completioncombines both categories: current-week EI self-assessment results and latest all-time personality type results for each reportable staff user.
Tests
src/services/personal_scope_results.test.tsverifies that parent users drilled into child scopes do not create personality quiz rows, that current-user EI reads are week-scoped, that profile history reads persisted rows for the current user, and that completion reporting combines weekly EI with all-time personality type results.
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).