Shared: types/schémas/codes d'erreur pour foyer admin/invitation + suppression de compte (step 1/8)

- HouseView gagne adminId/inviteCode/members (HouseMemberView)
- createHouseSchema, joinHouseSchema
- deleteAccountSchema (nouveau packages/shared/src/schemas/account.ts)
- ErrorCode: ALREADY_HAS_HOUSE, NOT_HOUSE_ADMIN, INVITE_CODE_NOT_FOUND
This commit is contained in:
Nicolas 2026-08-17 10:37:50 +02:00
parent 97ca5f88dd
commit 1248461125
5 changed files with 59 additions and 3 deletions

View file

@ -14,6 +14,8 @@
* code families * code families
* - `4000``4099`: request validation * - `4000``4099`: request validation
* - `4010``4019`: authentication * - `4010``4019`: authentication
* - `4020``4029`: conflicting/invalid state transition
* - `4030``4039`: authorization (caller authenticated, but not allowed to)
* - `4040``4049`: not found * - `4040``4049`: not found
* - `5000``5099`: internal/unexpected * - `5000``5099`: internal/unexpected
* *
@ -32,6 +34,10 @@ export enum ErrorCode {
INVALID_CREDENTIALS = 4010, INVALID_CREDENTIALS = 4010,
/** Request required a session cookie/JWT that is missing, invalid, or stale. */ /** Request required a session cookie/JWT that is missing, invalid, or stale. */
NOT_AUTHENTICATED = 4011, NOT_AUTHENTICATED = 4011,
/** `POST /house` or `POST /house/join` attempted while the profile already belongs to a household. */
ALREADY_HAS_HOUSE = 4020,
/** A household action reserved to its admin (delete the household, remove a member) attempted by a non-admin member. */
NOT_HOUSE_ADMIN = 4030,
/** No route/resource matches the request. */ /** No route/resource matches the request. */
NOT_FOUND = 4040, NOT_FOUND = 4040,
/** The profile making the request has no household yet (`houseId` is `null`). */ /** The profile making the request has no household yet (`houseId` is `null`). */
@ -40,6 +46,8 @@ export enum ErrorCode {
DIET_NOT_FOUND = 4042, DIET_NOT_FOUND = 4042,
/** One or more `allergyIds` don't match any reference `Allergy` row. */ /** One or more `allergyIds` don't match any reference `Allergy` row. */
ALLERGY_NOT_FOUND = 4043, ALLERGY_NOT_FOUND = 4043,
/** `POST /house/join`'s `inviteCode` doesn't match any household. */
INVITE_CODE_NOT_FOUND = 4044,
/** Unexpected/unhandled failure — the catch-all, always logged server-side. */ /** Unexpected/unhandled failure — the catch-all, always logged server-side. */
INTERNAL_ERROR = 5000, INTERNAL_ERROR = 5000,
} }

View file

@ -4,6 +4,7 @@
// detail specific to one side. // detail specific to one side.
export * from "./errors/error-codes.js"; export * from "./errors/error-codes.js";
export * from "./schemas/account.js";
export * from "./schemas/auth.js"; export * from "./schemas/auth.js";
export * from "./schemas/household.js"; export * from "./schemas/household.js";
export * from "./schemas/profile.js"; export * from "./schemas/profile.js";

View file

@ -0,0 +1,15 @@
import { z } from "zod";
// See schemas/auth.ts for the shared client/server validation rationale.
/**
* Payload accepted by `DELETE /auth/me`. Deleting an account is
* irreversible, so it's gated behind re-entering the current password
* same idea as `loginSchema`'s password field (presence only, the API does
* the real `argon2.verify`), not a fresh set of complexity rules.
*/
export const deleteAccountSchema = z.object({
password: z.string().min(1, "Le mot de passe est requis"),
});
/** Inferred TS type for {@link deleteAccountSchema}'s validated output. */
export type DeleteAccountInput = z.infer<typeof deleteAccountSchema>;

View file

@ -10,3 +10,17 @@ export const renameHouseSchema = z.object({
}); });
/** Inferred TS type for {@link renameHouseSchema}'s validated output. */ /** Inferred TS type for {@link renameHouseSchema}'s validated output. */
export type RenameHouseInput = z.infer<typeof renameHouseSchema>; export type RenameHouseInput = z.infer<typeof renameHouseSchema>;
/** Payload accepted by `POST /house` — same naming rule as renaming one. */
export const createHouseSchema = z.object({
name: z.string().trim().min(1, "Le nom du foyer est requis").max(100),
});
/** Inferred TS type for {@link createHouseSchema}'s validated output. */
export type CreateHouseInput = z.infer<typeof createHouseSchema>;
/** Payload accepted by `POST /house/join`. Invite codes are always 8 characters — see `house.service.ts`'s generator. */
export const joinHouseSchema = z.object({
inviteCode: z.string().trim().length(8, "Le code d'invitation doit contenir 8 caractères"),
});
/** Inferred TS type for {@link joinHouseSchema}'s validated output. */
export type JoinHouseInput = z.infer<typeof joinHouseSchema>;

View file

@ -1,9 +1,27 @@
/** /**
* A household, as returned by `GET /house/current` / `PATCH /house/current`. * One member of a household, as embedded in {@link HouseView}. Deliberately
* Unlike `DietView`/`AllergyView` this isn't reference data — it's the * a small subset of `SafeUserProfile` just enough for the household
* current user's own household. * settings page's member list (name + who's the admin), not the member's
* own email/diet/etc.
*/
export interface HouseMemberView {
id: number;
firstName: string;
lastName: string;
}
/**
* A household, as returned by `GET /house/current` / `PATCH /house/current`
* / `POST /house` / `POST /house/join`. Unlike `DietView`/`AllergyView`
* this isn't reference data — it's the current user's own household.
*/ */
export interface HouseView { export interface HouseView {
id: number; id: number;
name: string; name: string;
/** FK to the member who administers this household (created it, or inherited adminship — see `house.service.ts`'s `leaveCurrentHouse`). */
adminId: number;
/** Shareable code another user enters via `POST /house/join` to become a member. */
inviteCode: string;
/** Every member of this household, including the caller. */
members: HouseMemberView[];
} }