/** * Enumeration of every business/domain error code the API can return. * * This is the single source of truth for error identification across the * whole monorepo: `apps/api` throws errors carrying one of these codes, * and `apps/web` maps each code to a localized, user-facing label (see * `apps/web/src/services/error-message.service.ts`, backed by i18next * locale files under `apps/web/src/locales/`). Neither side should ever * hardcode a raw error value that the other side has to guess at — always * reference `ErrorCode.XXX`, never a bare number/string. * * Numeric values (not string codes): grouped by category so the number * itself hints at the kind of failure, similar in spirit to HTTP status * 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 * * When adding a new failure case in the API: * 1. Add a new member here, in the right range, with the next free number. * 2. Throw it via `HttpError` (`@batch-cooking/express-tools`). * 3. Add its translation key to every locale file under * `apps/web/src/locales` (one `translation.json` per language). */ export enum ErrorCode { /** Request body/query failed zod schema validation. */ VALIDATION_ERROR = 4000, /** Signup attempted with an email that already has a profile. */ EMAIL_ALREADY_IN_USE = 4001, /** Login failed — wrong email or wrong password (never say which). */ 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, /** `DELETE /recipes/:id` attempted on a recipe still referenced by at least one `PlanningItem`. */ RECIPE_IN_USE = 4021, /** `POST /sources/:sourceKey/import/:externalId` attempted on an item already imported (a `Recipe` already exists for that `sourceId`/`externalId` pair). */ RECIPE_ALREADY_IMPORTED = 4022, /** A household action reserved to its admin (delete the household, remove a member) attempted by a non-admin member. */ NOT_HOUSE_ADMIN = 4030, /** `PATCH /recipes/:id` or `DELETE /recipes/:id` attempted by someone other than the recipe's author — visibility controls reading, not writing. */ NOT_RECIPE_AUTHOR = 4031, /** No route/resource matches the request. */ NOT_FOUND = 4040, /** The profile making the request has no household yet (`houseId` is `null`). */ HOUSE_NOT_FOUND = 4041, /** A `dietId` was given that doesn't match any reference `Diet` row. */ 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, /** `GET /recipes/:id`, `PATCH /recipes/:id` or `DELETE /recipes/:id` given an id that doesn't match any recipe. */ RECIPE_NOT_FOUND = 4045, /** A recipe payload's `ingredientId` doesn't match any reference `Ingredient` row. */ INGREDIENT_NOT_FOUND = 4046, /** `DELETE /planning/items/:id` given an id that doesn't match any planning item visible to the caller's household. */ PLANNING_ITEM_NOT_FOUND = 4047, /** A recipe payload's `unitId` doesn't match any reference `Unit` row. */ UNIT_NOT_FOUND = 4048, /** `PATCH /house/current/sources`'s `sourceIds` contains one that doesn't match any reference `Source` row. */ SOURCE_NOT_FOUND = 4049, /** Unexpected/unhandled failure — the catch-all, always logged server-side. */ INTERNAL_ERROR = 5000, } /** * Shape of every JSON error body the API returns, whatever the failure. * Kept intentionally small and stable: `code` is what clients should * branch on, `message` is a human-readable (English, developer-facing) * description useful for logs/debugging — never shown to end users as-is, * since end-user-facing text is localized client-side from `code`. */ export interface ApiErrorResponse { /** Machine-readable error identifier — see {@link ErrorCode}. */ code: ErrorCode; /** Developer-facing description (English). Not localized, not for UI display. */ message: string; /** Present only for VALIDATION_ERROR: per-field error messages from zod. */ details?: Record; }