02 — User Personas (Users Module)
Persona base from
studylyon-blueprint/01-Product/USER_PERSONAS.md, refined for the Users module. Each persona lists module-specific goals, pains, and the exact capabilities the backend gives them (withfile:line).
1. Tenant personas acting on users
P1 — Org Admin (the user manager)
"Manages a single institution (tenant)... Goals: configure branches, invite staff, oversee operations." (
USER_PERSONAS.md:22-25)
- Role in this module: primary operator of every Users screen.
- Permissions granted by code:
user.read/create/update/delete/import+rbac.member.*exist in the permission catalogue (permissions.constants.ts:6-10,15-18); register seeds theorg_adminrole (auth.service.ts:84-99). - Goals
- Create a user (staff, teacher, parent) in seconds —
POST /users(users.controller.ts:36-40). - Find anyone:
GET /users?q=regex over first/last name, email, displayName, case-insensitive (users.service.ts:92-99). - Bulk-import a term-start spreadsheet:
POST /users/import(users.controller.ts:104-110) and get a per-row error list. - Grant/change roles:
POST/PATCH /rbac/members(rbac.controller.ts:63-73). - Deactivate or erase (GDPR):
DELETE /users/:id,POST /users/:id/erasure.
- Create a user (staff, teacher, parent) in seconds —
- Pains
- Duplicate emails are hard-rejected (409 —
users.service.ts:50-54,120-126); a typo blocks the whole row in import (Row N: email "x" already exists—users.service.ts:260-263). - No status/role filter on
GET /users(onlyq,sort, pagination —users.service.ts:90-115) → filters are(planned). - Import runs synchronously; a 1,000-row file blocks the request
(
users.service.ts:233-282) — see 14_QA_Checklist.md scenario C1.
- Duplicate emails are hard-rejected (409 —
- Success metric: add a staff member in < 60 s; import 500 rows with all errors locatable to a row number.
P2 — HR / Operations Staff (user manager delegate)
Staff persona: "operations, reception, coordinators... often read-heavy" (
USER_PERSONAS.md:34-39).
- Role: day-to-day user-list operator — new joiner intake, record cleanup,
status upkeep. Same screens as P1 but narrower permissions
(e.g.
user.read+user.create, nouser.delete). - Goals: search (name/email/displayName), view detail, update contact
info (
PATCH /users/:id), run the CSV import for new batches. - Pains: needs row-level role context on the users list (membership is a
separate API —
GET /rbac/members,rbac.controller.ts:57-61); without the join the list shows identities only. - Constraints enforced by code: soft-delete hides users from every list
(
base.repository.ts:20-30); if HR "removes" someone, they vanish — the UI must offer erasure vs. statusinactiveas distinct, labeled choices.
P3 — Teacher / Staff / Parent as managed users
Teacher: "wants minimal clicks; mobile-friendly" (
USER_PERSONAS.md:29-32); Parent: "wants proactive, timely communication" (USER_PERSONAS.md:47-50).
- Role: subject of admin management; consume self-service profile.
- What they can do today (self-service):
- Update their own profile data —
PATCH /users/:idaccepts name/email/ phone/gender/DOB/language/timezone/status/metadata (update-user.dto.ts:5-69). (Note: the controller does not restrict:idto self — client must enforce; server-side self-guard(planned), OQ-3.) - Preferences:
GET/PATCH /users/:id/preferences(users.controller.ts:78-93) — notification toggles and theme. - Avatar:
POST /users/:id/avatar(users.controller.ts:95-102).
- Update their own profile data —
- Pains
- Email change collides if the address is taken — 409 inline
(
users.service.ts:120-126). - Email verification state:
auth_accounts.emailVerifiedAtis written byverifyEmail(auth.service.ts:217-223) but the user-levelemailVerifiedflag on the schema (user.schema.ts:74-75) is never updated by that path — UI must not rely on it (OQ-4).
- Email change collides if the address is taken — 409 inline
(
P4 — Self-service user (any role)
- Same capabilities as P3 minus admin screens; cares about theme
(
preferences.theme.mode: light|dark|system—update-user-preferences.dto.ts:16-20), notification channels (email/push/smsbooleans —:8-12), and privacy. - Pain: no "account" endpoint that returns own profile — the client
resolves self by the JWT
suband callsGET /users/:id(OQ-3).
2. Platform persona
P5 — Super Admin (platform)
"Operates the StudyLyon SaaS itself... needs global visibility without touching tenant data" (
USER_PERSONAS.md:10-14).
- Role in this module: oversight only — user data stays tenant-scoped.
Platform admin bypasses tenant scope in repositories
(
base.repository.ts:21-23) butisPlatformAdminis a token flag; there is no platform users screen in the module surface (05_Global_IA has no platform section for users). Platform view of users(planned)if ever needed (audit use-case).
3. System personas
P6 — Email Engine (worker)
- Processes
emailsqueue (queue.constants.ts:2); handles onlyUserRegistered("Welcome to StudyLyon" + verify token) andPasswordResetRequested(email.worker.ts:25-42). - Consequence: admin-created users get no welcome/invite email today
(invite mail
(planned)); onboarding UI must say "account created" without promising an e-mail.
P7 — Purge Worker (GDPR)
tenant-purgequeue (queue.constants.ts:12); erases user doc ongdpr-erasurejob (tenant-purge.worker.ts:49-56); purges soft-deleted docs > 30 days (:32-43). Idempotent by design (:26).
P8 — Audit Logger
- Consumes
audit-writeforUserUpdated/UserDeleted(event-queue-map.ts:11-12); immutable perPLAN.md 19.3.
P9 — In-app Notifications
UserCreated→in-appqueue jobuser-created-notification(event-queue-map.ts:10) — admin gets notified of creations, but not of bulk-import rows (those bypass the event —users.service.ts:264-272).
4. Persona → screen map
| Persona | Screens (05_Screen_Inventory.md) | Permissions used |
|---|---|---|
| P1 Org Admin | Users list, detail, create, edit, bulk import, preferences (as manager) | user.*, rbac.member.* |
| P2 HR | Users list, detail, edit, bulk import | user.read, user.update/create |
| P3/P4 Teacher/Staff/Parent | Self-profile, preferences, avatar | self-scoped GET/PATCH /users/:id (client-enforced) |
| P5 Super Admin | none today (tenant data) | isPlatformAdmin |
| P6–P9 | none (background) | — |
Gap note: roles on the users list come from
GET /rbac/members(rbac.controller.ts:57-61), which requiresorg_adminrole (rbac.controller.ts:21-22) — a read-onlyuser.readuser cannot resolve role chips. Role display for non-admin viewers(planned).