From 9123f6358ec2c53e64cd542b4985f3310c020296 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 10 Sep 2026 20:59:08 +0200 Subject: [PATCH] =?UTF-8?q?feat(shared):=20sch=C3=A9ma=20et=20vues=20pour?= =?UTF-8?q?=20la=20cr=C3=A9ation=20de=20comptes=20par=20un=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `adminCreateUserSchema` (prénom/nom/email, sans mot de passe — généré côté serveur) + les vues `AdminUserListItemView` / `AdminUserCreatedView` consommées par `POST`/`GET /admin/api/users`. Co-Authored-By: Claude Sonnet 5 --- packages/shared/src/schemas/admin.ts | 18 ++++++++++++++++ packages/shared/src/types/admin.ts | 31 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/shared/src/schemas/admin.ts b/packages/shared/src/schemas/admin.ts index 7f6bfb8..f7fc3e7 100644 --- a/packages/shared/src/schemas/admin.ts +++ b/packages/shared/src/schemas/admin.ts @@ -17,6 +17,24 @@ export const adminLoginSchema = z.object({ /** Inferred TS type for {@link adminLoginSchema}'s validated output. */ export type AdminLoginInput = z.infer; +/** + * Payload accepted by `POST /admin/api/users` — an operator provisioning an + * end-user account by hand (test accounts, pre-registration, support). Its + * own contract, not a re-export of `signupSchema` (same stance as + * {@link adminLoginSchema} vs `loginSchema`), and notably **without a + * `password` field**: the server generates a one-time temporary password + * and returns it once (see `apps/api`'s `admin-users.service.ts`). The + * name/email rules mirror `signupSchema` so a hand-created account is + * indistinguishable from a self-signed-up one afterwards. + */ +export const adminCreateUserSchema = z.object({ + firstName: z.string().trim().min(1, "Le prénom est requis").max(100), + lastName: z.string().trim().min(1, "Le nom est requis").max(100), + email: z.string().trim().toLowerCase().email("Email invalide"), +}); +/** Inferred TS type for {@link adminCreateUserSchema}'s validated output. */ +export type AdminCreateUserInput = z.infer; + /** * Query params for `GET /admin/api/metrics` — `?days=` bounds how far back the * time-series go (and how many daily buckets they carry). Coerced from the diff --git a/packages/shared/src/types/admin.ts b/packages/shared/src/types/admin.ts index 978b583..a0ae6aa 100644 --- a/packages/shared/src/types/admin.ts +++ b/packages/shared/src/types/admin.ts @@ -15,6 +15,37 @@ export interface AdminUserView { lastLoginAt: string | null; } +/** + * One row of `GET /admin/api/users` — an end-user account as shown in the + * admin's user list. Declared by hand rather than derived from the Prisma + * `UserProfile` type (same reason as {@link AdminUserView} / {@link + * SafeUserProfile}: apps/web must not depend on `@prisma/client`), and + * deliberately *not* {@link SafeUserProfile}: this view drops `tokenVersion` + * (internal) and adds `householdName` (resolved server-side) + `createdAt`. + * `householdName` is `null` when the account belongs to no household; + * `createdAt` is an ISO 8601 string. + */ +export interface AdminUserListItemView { + id: number; + firstName: string; + lastName: string; + email: string; + householdName: string | null; + createdAt: string; +} + +/** + * Response of `POST /admin/api/users`. Carries the freshly-created account + * plus the **one-time** temporary password the server generated for it — + * never stored in plain text, never retrievable again, so the operator must + * copy it now and relay it to the user out of band. + */ +export interface AdminUserCreatedView { + user: AdminUserListItemView; + /** Plain-text temporary password, shown exactly once. */ + temporaryPassword: string; +} + /** One `{ key, count }` breakdown row — e.g. recipes per source, corrections per status. */ export interface MetricsBreakdownRow { key: string;