diff --git a/apps/api/src/lib/logger.service.ts b/apps/api/src/lib/logger.service.ts index a8685ae..a1c23c0 100644 --- a/apps/api/src/lib/logger.service.ts +++ b/apps/api/src/lib/logger.service.ts @@ -60,30 +60,30 @@ const CONSOLE_METHOD: Record = { /** Server-side operational logger — see the module doc comment for why this exists and what it's for. */ export class LoggerService { /** Computed once at construction from `env.NODE_ENV` — see {@link minLevelFor}. */ - private readonly minSeverity: number; + private readonly _minSeverity: number; public constructor(nodeEnv: typeof env.NODE_ENV = env.NODE_ENV) { - this.minSeverity = LEVEL_SEVERITY[minLevelFor(nodeEnv)]; + this._minSeverity = LEVEL_SEVERITY[minLevelFor(nodeEnv)]; } public debug(message: string, meta?: LogMeta): void { - this.emit("debug", message, meta); + this._emit("debug", message, meta); } public info(message: string, meta?: LogMeta): void { - this.emit("info", message, meta); + this._emit("info", message, meta); } public warn(message: string, meta?: LogMeta): void { - this.emit("warn", message, meta); + this._emit("warn", message, meta); } public error(message: string, meta?: LogMeta): void { - this.emit("error", message, meta); + this._emit("error", message, meta); } - private emit(level: LogLevel, message: string, meta?: LogMeta): void { - if (LEVEL_SEVERITY[level] < this.minSeverity) return; + private _emit(level: LogLevel, message: string, meta?: LogMeta): void { + if (LEVEL_SEVERITY[level] < this._minSeverity) return; // `meta` spread first so a caller accidentally passing e.g. `{ message: // ... }` in it can never shadow the line's own core fields. diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index 38403c7..aaf952a 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -71,7 +71,7 @@ export class ApiClient { * * @throws {ApiError} if the response status is not in the 2xx range. */ - private async request( + private async _request( path: string, options: RequestInit = {}, ): Promise { @@ -102,27 +102,27 @@ export class ApiClient { /** Creates a profile and starts a session — no household yet, that's an optional step of the onboarding wizard. */ public signup(input: SignupInput): Promise { - return this.request("/auth/signup", { method: "POST", body: JSON.stringify(input) }); + return this._request("/auth/signup", { method: "POST", body: JSON.stringify(input) }); } /** Verifies credentials and starts a session. */ public login(input: LoginInput): Promise { - return this.request("/auth/login", { method: "POST", body: JSON.stringify(input) }); + return this._request("/auth/login", { method: "POST", body: JSON.stringify(input) }); } /** Ends the current session. */ public logout(): Promise { - return this.request("/auth/logout", { method: "POST" }); + return this._request("/auth/logout", { method: "POST" }); } /** Fetches the currently authenticated profile — rejects with `NOT_AUTHENTICATED` if there's no session. */ public me(): Promise { - return this.request("/auth/me"); + return this._request("/auth/me"); } /** Permanently deletes the current profile, after re-verifying its password — rejects with `INVALID_CREDENTIALS` if it's wrong. */ public deleteAccount(password: string): Promise { - return this.request("/auth/me", { method: "DELETE", body: JSON.stringify({ password }) }); + return this._request("/auth/me", { method: "DELETE", body: JSON.stringify({ password }) }); } /** @@ -131,7 +131,7 @@ export class ApiClient { * there isn't one for that week yet. */ public getPlanningForWeek(date: string): Promise { - return this.request(`/planning?date=${date}`); + return this._request(`/planning?date=${date}`); } /** @@ -142,37 +142,37 @@ export class ApiClient { * caller). */ public addPlanningItem(input: AddPlanningItemInput): Promise { - return this.request("/planning/items", { method: "POST", body: JSON.stringify(input) }); + return this._request("/planning/items", { method: "POST", body: JSON.stringify(input) }); } /** Removes one recipe from a planning slot — rejects with `PLANNING_ITEM_NOT_FOUND`. */ public removePlanningItem(id: number): Promise { - return this.request(`/planning/items/${id}`, { method: "DELETE" }); + return this._request(`/planning/items/${id}`, { method: "DELETE" }); } /** Reference list of dietary regimes to pick from (signup wizard, `/foyer`). Public — no session required. */ public getDiets(): Promise { - return this.request("/reference/diets"); + return this._request("/reference/diets"); } /** Reference list of selectable allergens (signup wizard, `/foyer`). Public — no session required. */ public getAllergies(): Promise { - return this.request("/reference/allergies"); + return this._request("/reference/allergies"); } /** Reference list of ingredients, each resolved to its allergens — static, non-administrable (recipe form's ingredient picker). Public — no session required. */ public getIngredients(): Promise { - return this.request("/reference/ingredients"); + return this._request("/reference/ingredients"); } /** Reference list of recipe ingredient units (g, kg, cuillère à soupe…) — static, non-administrable (recipe form's per-ingredient unit select). Public — no session required. */ public getUnits(): Promise { - return this.request("/reference/units"); + return this._request("/reference/units"); } /** Reference list of implemented recipe sources (onboarding wizard's source step, `/parametres/foyer`) — empty until a concrete source is registered. Public — no session required. */ public getSources(): Promise { - return this.request("/reference/sources"); + return this._request("/reference/sources"); } /** One page of `sourceKey`'s own catalog (recipe catalog's "Sources" tab), each item flagged with whether it's already been imported. Rejects with `SOURCE_NOT_FOUND` unless the viewer's household has this source enabled (`/parametres/foyer`). */ @@ -184,12 +184,12 @@ export class ApiClient { if (params.query) search.set("query", params.query); if (params.cursor) search.set("cursor", params.cursor); const queryString = search.toString(); - return this.request(`/sources/${sourceKey}/browse${queryString ? `?${queryString}` : ""}`); + return this._request(`/sources/${sourceKey}/browse${queryString ? `?${queryString}` : ""}`); } /** Fully translates one not-yet-saved source item (ingredients/units/techniques resolved where possible) — nothing is persisted. Rejects with `RECIPE_NOT_FOUND` if the source couldn't fetch/parse it. */ public previewSourceItem(sourceKey: string, externalId: string): Promise { - return this.request(`/sources/${sourceKey}/preview/${encodeURIComponent(externalId)}`); + return this._request(`/sources/${sourceKey}/preview/${encodeURIComponent(externalId)}`); } /** Finalizes an import — `input` is a fully-resolved `CreateRecipeInput`, exactly like a manual `createRecipe()` call (the review screen, `ImportRecipePage`, is what makes sure of that before calling this). Rejects with `RECIPE_ALREADY_IMPORTED` if this item was imported since the preview was fetched. */ @@ -198,7 +198,7 @@ export class ApiClient { externalId: string, input: CreateRecipeInput, ): Promise { - return this.request(`/sources/${sourceKey}/import/${encodeURIComponent(externalId)}`, { + return this._request(`/sources/${sourceKey}/import/${encodeURIComponent(externalId)}`, { method: "POST", body: JSON.stringify(input), }); @@ -225,82 +225,82 @@ export class ApiClient { if (filters.suitableForHousehold) params.set("suitableForHousehold", "true"); for (const id of filters.ingredientIds ?? []) params.append("ingredientIds", String(id)); for (const id of filters.dietIds ?? []) params.append("dietIds", String(id)); - return this.request(`/recipes?${params.toString()}`); + return this._request(`/recipes?${params.toString()}`); } /** Fetches one recipe's full detail — rejects with `RECIPE_NOT_FOUND` if `id` doesn't match any recipe. */ public getRecipe(id: number): Promise { - return this.request(`/recipes/${id}`); + return this._request(`/recipes/${id}`); } /** Adds a recipe to the catalog — rejects with `INGREDIENT_NOT_FOUND` if any `ingredientId` doesn't match a reference ingredient. */ public createRecipe(input: CreateRecipeInput): Promise { - return this.request("/recipes", { method: "POST", body: JSON.stringify(input) }); + return this._request("/recipes", { method: "POST", body: JSON.stringify(input) }); } /** Replaces a recipe's full content (not a partial merge) — same rejections as {@link createRecipe}, plus `RECIPE_NOT_FOUND`. */ public updateRecipe(id: number, input: UpdateRecipeInput): Promise { - return this.request(`/recipes/${id}`, { method: "PATCH", body: JSON.stringify(input) }); + return this._request(`/recipes/${id}`, { method: "PATCH", body: JSON.stringify(input) }); } /** Removes a recipe from the catalog outright — rejects with `RECIPE_IN_USE` if it's still referenced by a planning item. */ public deleteRecipe(id: number): Promise { - return this.request(`/recipes/${id}`, { method: "DELETE" }); + return this._request(`/recipes/${id}`, { method: "DELETE" }); } /** Favorites a recipe for the current user — idempotent. */ public addFavoriteRecipe(id: number): Promise { - return this.request(`/recipes/${id}/favorite`, { method: "POST" }); + return this._request(`/recipes/${id}/favorite`, { method: "POST" }); } /** Unfavorites a recipe for the current user — idempotent. */ public removeFavoriteRecipe(id: number): Promise { - return this.request(`/recipes/${id}/favorite`, { method: "DELETE" }); + return this._request(`/recipes/${id}/favorite`, { method: "DELETE" }); } /** Fetches the current user's household (with its member list), or `null` if they don't have one yet. */ public getCurrentHouse(): Promise { - return this.request("/house/current"); + return this._request("/house/current"); } /** Renames the current user's household. */ public renameHouse(name: string): Promise { - return this.request("/house/current", { method: "PATCH", body: JSON.stringify({ name }) }); + return this._request("/house/current", { method: "PATCH", body: JSON.stringify({ name }) }); } /** Creates a new household, with the caller as its admin — rejects with `ALREADY_HAS_HOUSE` if they already belong to one. */ public createHouse(name: string): Promise { - return this.request("/house", { method: "POST", body: JSON.stringify({ name }) }); + return this._request("/house", { method: "POST", body: JSON.stringify({ name }) }); } /** Joins an existing household by invite code — rejects with `ALREADY_HAS_HOUSE`/`INVITE_CODE_NOT_FOUND`. */ public joinHouse(inviteCode: string): Promise { - return this.request("/house/join", { method: "POST", body: JSON.stringify({ inviteCode }) }); + return this._request("/house/join", { method: "POST", body: JSON.stringify({ inviteCode }) }); } /** Removes the current user from their household — hands off adminship or deletes the household if they were its last member (see the API's `house.service.ts`). */ public leaveHouse(): Promise { - return this.request("/house/leave", { method: "POST" }); + return this._request("/house/leave", { method: "POST" }); } /** Deletes the current user's household outright — every member loses it. Admin-only. */ public deleteHouse(): Promise { - return this.request("/house/current", { method: "DELETE" }); + return this._request("/house/current", { method: "DELETE" }); } /** Removes one specific member from the current user's household. Admin-only. */ public removeHouseMember(memberId: number): Promise { - return this.request(`/house/members/${memberId}`, { method: "DELETE" }); + return this._request(`/house/members/${memberId}`, { method: "DELETE" }); } /** Fetches the current user's household's enabled recipe-source ids — which sources show up in its recipe tabs. Rejects with `HOUSE_NOT_FOUND` if they have no household yet. */ public getHouseSourceIds(): Promise { - return this.request("/house/current/sources"); + return this._request("/house/current/sources"); } /** Replaces the current user's household's full enabled-source selection (not a merge — send the complete list; empty hides every external source). Rejects with `HOUSE_NOT_FOUND`/`SOURCE_NOT_FOUND`. */ public updateHouseSourceIds(sourceIds: number[]): Promise { - return this.request("/house/current/sources", { + return this._request("/house/current/sources", { method: "PATCH", body: JSON.stringify({ sourceIds }), }); @@ -308,17 +308,17 @@ export class ApiClient { /** Sets (or clears, with `null`) the current user's dietary regime. */ public updateDiet(dietId: number | null): Promise { - return this.request("/profile/diet", { method: "PATCH", body: JSON.stringify({ dietId }) }); + return this._request("/profile/diet", { method: "PATCH", body: JSON.stringify({ dietId }) }); } /** Fetches the current user's selected allergen ids. */ public getAllergyIds(): Promise { - return this.request("/profile/allergies"); + return this._request("/profile/allergies"); } /** Replaces the current user's full allergen selection (not a merge — send the complete list). */ public updateAllergyIds(allergyIds: number[]): Promise { - return this.request("/profile/allergies", { + return this._request("/profile/allergies", { method: "PATCH", body: JSON.stringify({ allergyIds }), }); @@ -326,12 +326,12 @@ export class ApiClient { /** Fetches the current user's personally disliked ingredient ids — a taste preference, distinct from `getAllergyIds` (medical). */ public getDislikedIngredientIds(): Promise { - return this.request("/profile/disliked-ingredients"); + return this._request("/profile/disliked-ingredients"); } /** Replaces the current user's full disliked-ingredient selection (not a merge — send the complete list). */ public updateDislikedIngredientIds(dislikedIngredientIds: number[]): Promise { - return this.request("/profile/disliked-ingredients", { + return this._request("/profile/disliked-ingredients", { method: "PATCH", body: JSON.stringify({ dislikedIngredientIds }), }); @@ -339,12 +339,12 @@ export class ApiClient { /** Fetches the current user's personalization preferences — `theme` defaults to `"SYSTEM"` if never set. */ public getPreferences(): Promise { - return this.request("/preferences"); + return this._request("/preferences"); } /** Sets the current user's theme preference. */ public updatePreferences(theme: ThemePreference): Promise { - return this.request("/preferences", { method: "PATCH", body: JSON.stringify({ theme }) }); + return this._request("/preferences", { method: "PATCH", body: JSON.stringify({ theme }) }); } } diff --git a/packages/error-tools/src/error-handler.service.ts b/packages/error-tools/src/error-handler.service.ts index c04f403..c591cee 100644 --- a/packages/error-tools/src/error-handler.service.ts +++ b/packages/error-tools/src/error-handler.service.ts @@ -31,16 +31,16 @@ export class ErrorHandlerService { */ public handle(error: unknown): ErrorHandlingResult { if (error instanceof ZodError) { - return this.fromZodError(error); + return this._fromZodError(error); } if (error instanceof HttpError) { - return this.fromHttpError(error); + return this._fromHttpError(error); } - return this.fromUnknownError(error); + return this._fromUnknownError(error); } /** Request body/query failed schema validation — always a 400. */ - private fromZodError(error: ZodError): ErrorHandlingResult { + private _fromZodError(error: ZodError): ErrorHandlingResult { return { status: 400, body: { @@ -52,7 +52,7 @@ export class ErrorHandlerService { } /** Our own typed error — status/code were decided by whoever threw it. */ - private fromHttpError(error: HttpError): ErrorHandlingResult { + private _fromHttpError(error: HttpError): ErrorHandlingResult { return { status: error.status, body: { code: error.code, message: error.message }, @@ -68,7 +68,7 @@ export class ErrorHandlerService { * which sees every error — including this exact "unrecognized" case — * before it ever reaches here). */ - private fromUnknownError(_error: unknown): ErrorHandlingResult { + private _fromUnknownError(_error: unknown): ErrorHandlingResult { return { status: 500, body: { code: ErrorCode.INTERNAL_ERROR, message: "Internal server error" }, diff --git a/packages/express-tools/src/express-server.ts b/packages/express-tools/src/express-server.ts index 63368cd..ceb1473 100644 --- a/packages/express-tools/src/express-server.ts +++ b/packages/express-tools/src/express-server.ts @@ -31,17 +31,17 @@ export interface ExpressServerCoreOptions { */ export class ExpressServer { /** The underlying Express application. */ - private readonly app: Express; + private readonly _app: Express; /** Tracks `"METHOD path"` keys already registered via {@link addRoute}, to warn instead of silently double-registering a route. */ - private readonly registeredRoutes = new Set(); + private readonly _registeredRoutes = new Set(); public constructor() { - this.app = express(); + this._app = express(); } /** The underlying Express application — needed by test tooling (e.g. supertest) that expects a raw `Express` instance. */ public get instance(): Express { - return this.app; + return this._app; } /** @@ -51,14 +51,14 @@ export class ExpressServer { * any route. */ public setupCore(options: ExpressServerCoreOptions): void { - this.app.use(cors({ origin: options.corsOrigin, credentials: true })); - this.app.use(express.json()); - this.app.use(cookieParser()); + this._app.use(cors({ origin: options.corsOrigin, credentials: true })); + this._app.use(express.json()); + this._app.use(cookieParser()); } /** Registers a middleware that runs on every request (e.g. logging, a catch-all 404 handler). */ public addMiddleware(middleware: RequestHandler): void { - this.app.use(middleware); + this._app.use(middleware); } /** @@ -67,12 +67,12 @@ export class ExpressServer { * error handler by its arity, and only the last matching one runs. */ public setErrorHandler(middleware: ErrorRequestHandler): void { - this.app.use(middleware); + this._app.use(middleware); } /** Mounts a whole `express.Router` under a base path (e.g. `mountRouter("/auth", authRouter)`). */ public mountRouter(basePath: string, router: Router): void { - this.app.use(basePath, router); + this._app.use(basePath, router); } /** @@ -88,8 +88,8 @@ export class ExpressServer { * this, so `pnpm dev:web`'s own Vite dev server is unaffected. */ public serveStaticFrontend(distDir: string): void { - this.app.use(express.static(distDir)); - this.app.get("*", (_req, res) => { + this._app.use(express.static(distDir)); + this._app.get("*", (_req, res) => { res.sendFile(path.join(distDir, "index.html")); }); } @@ -101,16 +101,16 @@ export class ExpressServer { */ public addRoute(method: HttpMethod, path: string, ...handlers: RequestHandler[]): void { const key = `${method.toUpperCase()} ${path}`; - if (this.registeredRoutes.has(key)) { + if (this._registeredRoutes.has(key)) { console.warn(`[ExpressServer] Route already registered, skipping: ${key}`); return; } - this.registeredRoutes.add(key); - this.app[method](path, ...handlers); + this._registeredRoutes.add(key); + this._app[method](path, ...handlers); } /** Starts listening on the given port. `onListening` is called once the server is up (e.g. to log the URL). */ public listen(port: number, onListening?: () => void): void { - this.app.listen(port, onListening); + this._app.listen(port, onListening); } }