Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

08 — Form Specifications (Users Module)

Every field of every form, derived 1:1 from create-user.dto.ts, update-user.dto.ts, update-user-preferences.dto.ts, and user.schema.ts, plus upload fields and CSV column specs from users.service.ts:233-282 and students-import.adapter.ts:17-26. Validation column = server enforcement (class-validator / schema).


1. Create user form (POST /api/v1/usersCreateUserDto)

#FieldTypeRequiredServer validationDefaultNotes
1firstNametext@IsString (create-user.dto.ts:7-8)Schema required: true (user.schema.ts:16-17)
2middleNametext@IsOptional @IsString (:10-13)
3lastNametext@IsString (:15-17)Schema required (:22-23)
4displayNametext@IsOptional @IsString (:19-22)firstName + " " + lastName (users.service.ts:62)Recomputed on update (:136-139)
5emailemail@IsEmail (:24-26); schema lowercase+trim (user.schema.ts:28-29)Unique per tenant (user.schema.ts:83); server 409 pre-check (users.service.ts:50-54)
6phonetel@IsOptional @IsString (:28-31); schema trim (:31-32)Unique per tenant (partial index :84-90); 409 pre-check (users.service.ts:56-60)
7genderselect@IsOptional @IsString (:33-38)Schema enum male/female/other/prefer_not_to_say (user.schema.ts:37-41); client constrains, server is string-typed (mismatch OQ-12)
8dateOfBirthdateoptional, no decorator beyond Optional (:40-42)Schema Date (:43-44); max = today
9statusselect@IsEnum(UserStatus) (:44-47)activeEnum active/inactive/suspended/invited (user.schema.ts:7-12); invited only via (planned) invite flow
10languagetext@IsOptional @IsString (:49-52)enSchema default en (:49-50)
11timezonetext@IsOptional @IsString (:54-57)UTCSchema default UTC (:52-53); use searchable AppDropdown
12metadatajson@IsOptional object (:59-61)Schema Object (user.schema.ts:68-69); not exposed as a user field by default (proposed)

Submission semantics

  • Tenant: never sent by client — derived from JWT (00-shared/07 §6; repo injects tenantIdbase.repository.ts:32-36).
  • Success: 201 envelope with user doc; then (if roles chosen) POST /rbac/members {userId, roles} (rbac.controller.ts:63-67).
  • Errors: 400 field-level; 409 email/phone (message text from users.service.ts:50-60); 429 backoff; 5xx generic.

2. Edit user form (PATCH /api/v1/users/:idUpdateUserDto)

#FieldNotes (same validation as §1 where present)
1firstName / middleName / lastNameoptional @IsString (update-user.dto.ts:8-24); renaming recomputes displayName (users.service.ts:136-139)
2displayNameexplicit override wins over computed (:136-139)
3email@IsEmail (:26-29); change → uniqueness re-check vs. existing (users.service.ts:120-126)
4phone@IsString (:30-33); change → uniqueness re-check (:128-134)
5avatarFileId@IsString (:38-39) — not a user-facing field; avatar managed via POST /users/:id/avatar
6gender / dateOfBirth / status / language / timezone / metadatasame as §1 (:41-69); status enum-validated (:52-55)
  • Partial PATCH: only changed fields sent; $set merge + version increment (base.repository.ts:57-66).

3. Preferences form (PATCH /api/v1/users/:id/preferencesUpdateUserPreferencesDto)

#GroupFieldTypeServer validationNotes
1notificationsemailboolean@IsOptional @IsObject group (update-user-preferences.dto.ts:4-12)Schema Boolean (user.schema.ts:56-66)
2notificationspushbooleansame
3notificationssmsbooleansame
4thememoderadio@IsOptional @IsObject (:14-20)Schema enum light/dark/system (user.schema.ts:61)
5themelanguagetextsame

Contract warning: updatePreferences does $set: { preferences: dto } (users.service.ts:161-163) — a full replace. Client always sends the merged complete object; a partial payload wipes the other group (e.g. sending only {theme} drops notifications).

Read shape: GET /users/:id/preferencesuser.preferences ?? {} (users.service.ts:168-171).

4. Avatar upload (POST /api/v1/users/:id/avatar)

FieldValue
Multipart field namefileFileInterceptor('file') (users.controller.ts:96)
Content typemultipart/form-data (ApiConsumes :97)
Payloadimage buffer; filename prefix ${id}- server-side (users.service.ts:219)
Response{ avatarFileId } (:230); previous avatar deleted best-effort (:227-229)
Validationnone in code (no size/mime check server-side) — client: image types, ≤ 2 MB, square crop (OQ-10)
Storage pathsl/{tenantId}/avatars/{uuid} blueprint (04-Modules/Users.md:66)

5. Users CSV import columns (POST /api/v1/users/import)

Endpoint parses rows keyed by lowercased header (users.service.ts:244-247):

Column (accepted headers)RequiredServer behavior
emailmissing → Row N: missing email (:256-259); duplicate (existing user or earlier row) → Row N: email "x" already exists (:260-263)
firstname / first_namefallback Unknown (:265)
lastname / last_namefallback Unknown (:266)
phonepassed through if present (:268)
genderpassed through (:269)
languagedefault en (:270)
timezonedefault UTC (:271)
  • Header row required + ≥ 1 data row (:238-243); empty file → errors contains "CSV must have a header row and at least one data row".
  • Naive , split — quoted commas unsupported (OQ-7); empty lines skipped (:237).
  • Success shape: { imported: number, errors: string[] } (:281).
  • Template download header (client): email,firstname,lastname,phone,gender, language,timezone.

6. Bulk-module CSV (POST /api/v1/bulk/import/:entity)

  • Parser: csv-parse/sync {columns:true, skip_empty_lines:true, trim:true} (bulk-import.service.ts:26-30) — supports quoted fields; malformed → 400 VALIDATION_ERROR (:31-33); no rows → 400 (:34-35).
  • Report shape: {entity, totalRows, imported, failed, errors:[{rowNumber, errors[]}]} (import-adapter.interface.ts:14-25); rowNumber is physical line (index + 2, header = row 1 — bulk-import.service.ts:46).
  • students adapter columns (students-import.adapter.ts:17-26): required firstName, lastName, email, admissionNumber, grade, section, academicYear; optional rollNumber. Validation: required presence (:41-45), email regex (:46-47), admission-number dup (:49-54), email registered dup (:55-56), ref resolution grade/section/academicYear/class (:58,100-144).
  • No users adapter yetentity='users' → 404 "No import adapter" (bulk-import.service.ts:17-20) (planned).

7. Form state conventions (client)