diff --git a/packages/shared/src/errors/error-codes.ts b/packages/shared/src/errors/error-codes.ts index b245726..d70609d 100644 --- a/packages/shared/src/errors/error-codes.ts +++ b/packages/shared/src/errors/error-codes.ts @@ -14,6 +14,8 @@ * code families — * - `4000`–`4099`: request validation * - `4010`–`4019`: authentication + * - `4020`–`4029`: conflicting/invalid state transition + * - `4030`–`4039`: authorization (caller authenticated, but not allowed to) * - `4040`–`4049`: not found * - `5000`–`5099`: internal/unexpected * @@ -32,6 +34,10 @@ export enum ErrorCode { INVALID_CREDENTIALS = 4010, /** Request required a session cookie/JWT that is missing, invalid, or stale. */ 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. */ NOT_FOUND = 4040, /** The profile making the request has no household yet (`houseId` is `null`). */ @@ -40,6 +46,8 @@ export enum ErrorCode { DIET_NOT_FOUND = 4042, /** One or more `allergyIds` don't match any reference `Allergy` row. */ 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. */ INTERNAL_ERROR = 5000, } diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 8f20709..95aa7b9 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -4,6 +4,7 @@ // detail specific to one side. export * from "./errors/error-codes.js"; +export * from "./schemas/account.js"; export * from "./schemas/auth.js"; export * from "./schemas/household.js"; export * from "./schemas/profile.js"; diff --git a/packages/shared/src/schemas/account.ts b/packages/shared/src/schemas/account.ts new file mode 100644 index 0000000..a8a9f77 --- /dev/null +++ b/packages/shared/src/schemas/account.ts @@ -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; diff --git a/packages/shared/src/schemas/household.ts b/packages/shared/src/schemas/household.ts index bd138aa..df95e94 100644 --- a/packages/shared/src/schemas/household.ts +++ b/packages/shared/src/schemas/household.ts @@ -10,3 +10,17 @@ export const renameHouseSchema = z.object({ }); /** Inferred TS type for {@link renameHouseSchema}'s validated output. */ export type RenameHouseInput = z.infer; + +/** 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; + +/** 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; diff --git a/packages/shared/src/types/household.ts b/packages/shared/src/types/household.ts index 32b7c93..d519ede 100644 --- a/packages/shared/src/types/household.ts +++ b/packages/shared/src/types/household.ts @@ -1,9 +1,27 @@ /** - * A household, as returned by `GET /house/current` / `PATCH /house/current`. - * Unlike `DietView`/`AllergyView` this isn't reference data — it's the - * current user's own household. + * One member of a household, as embedded in {@link HouseView}. Deliberately + * a small subset of `SafeUserProfile` — just enough for the 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 { id: number; 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[]; }