Julien Gautier 0817520e49
CI / scan (pull_request) Successful in 2m29s
CI / commits (pull_request) Successful in 2m45s
CI / check (pull_request) Successful in 2m59s
CI / a11y (pull_request) Successful in 2m27s
CI / perf (pull_request) Successful in 7m2s
feat(portal-bff): admin user-directory read endpoint
Second PR of the portal-admin User-list chantier per ADR-0020 §"v1
scope — User list (read-only)". Ships the read side: paginated,
filterable HTTP endpoint that queries the `public.users` directory
populated at sign-in by PR #140. The SPA viewer screen lands in the
final PR of the chantier.

What lands

- AdminUsersQueryDto (admin/users-query.dto.ts): mirrors
  AdminAuditQueryDto's posture — filters all optional, every
  unknown key rejected by `forbidNonWhitelisted`, limit capped at
  MAX_LIMIT (200) / default 50. Filters: username (exact prefix),
  displayName (case-insensitive contains), audience (workforce |
  customer enum), lastSeenAtFrom/To (ISO-8601).

- AdminUsersReader (admin/admin-users-reader.service.ts): Prisma
  typed client against `public.users` — no `SET LOCAL ROLE` dance
  because `public.users` has no role-based privilege gate; the
  trust boundary is the controller's @RequireAdmin guard. Order:
  `last_seen_at DESC, oid ASC` (the second clause is a deterministic
  tie-breaker for pagination during sign-in bursts that share a
  timestamp). COUNT + SELECT run in a single Prisma transaction so
  the `total` reported to the SPA matches what's on the page even
  under a concurrent sign-in.

- AdminUsersController (admin/admin-users.controller.ts):
  GET /api/admin/users, @RequireAdmin at the class level, forwards
  the validated DTO to AdminUsersReader, then emits
  admin.users.query with { filters, resultCount } as the
  fishing-expedition deterrent (mirror of admin.audit.query from
  PR #132).

- AuditWriter.adminUsersQuery() typed method + AdminUsersQueryInput
  type. Same outcome=success / payload shape as adminAuditQuery —
  two distinct event types so a reviewer can pivot directly on
  eventType without parsing payload.

Tests: +18 specs (DTO validation 9, reader 9 covering COUNT+SELECT
ordering, filter forwarding, default/cap on limit, range filter
composition; controller 6; audit typed method 2). All 391 BFF
specs pass.

Out of scope (next PR of the chantier):

- portal-admin /users screen — the SPA viewer with filter form +
  table + pagination, mirroring the /audit page that the audit
  viewer PR shipped.
- Sign-in counts joined from `audit.events` on `actor_id_hash`
  (computed via HashUserIdService on the fly). Deferred until the
  v1 list ships and the admin demand makes itself known.
2026-05-14 19:56:11 +02:00

Documentation index

This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.

Conventions

  • Documentation is written in English.
  • One topic per file. Group related files into a folder when there are three or more.
  • Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
  • For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.

Sections

Daily development

  • development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.

Architecture

  • architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.

Onboarding & environment

Setup guides for new contributors:

Operations & runbooks

Empty — to be populated when we deploy.

Security, performance, accessibility

Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.

S
Description
No description provided
Readme 42 MiB
Languages
TypeScript 85.3%
JavaScript 5.4%
SCSS 4.3%
HTML 3.9%
Shell 0.8%
Other 0.3%