import { parseDateOnly } from "@batch-cooking/date-tools"; import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; import { addPlanningItemSchema, ErrorCode, getPlanningByDateSchema } from "@batch-cooking/shared"; import { Router } from "express"; import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js"; import { addPlanningItem, getPlanningForDate, removePlanningItem } from "./planning.service.js"; /** Router mounted at `/planning` in app.ts. */ export const planningRouter = Router(); /** * Returns the authenticated user's household's planning covering `?date=` * (`YYYY-MM-DD`), or `null` if none exists yet — a valid, common response, * not an error (see {@link getPlanningForDate}). Used both for "today" * (the planning page's initial load) and for any other week the planning * page's week navigator/calendar picks. */ planningRouter.get( "/", requireAuth, wrapAsyncHandler(async (req, res) => { const input = getPlanningByDateSchema.parse(req.query); const date = parseDateOnly(input.date); if (date === null) { throw new HttpError( 400, ErrorCode.VALIDATION_ERROR, `Not a real calendar date: ${input.date}`, ); } const planning = await getPlanningForDate(res.locals.userProfile.houseId, date); res.status(200).json(planning); }), ); /** Parses and validates the `:id` route param shared by every `/items/:id` route below. */ function parsePlanningItemId(rawId: string | undefined): number { const id = Number(rawId); if (!Number.isInteger(id)) { throw new HttpError(400, ErrorCode.VALIDATION_ERROR, "id must be an integer"); } return id; } /** * Adds a recipe to one (day, meal) slot of the authenticated user's * household's planning, creating that week's `Planning` row on the fly if * needed (see {@link addPlanningItem}). */ planningRouter.post( "/items", requireAuth, wrapAsyncHandler(async (req, res) => { const input = addPlanningItemSchema.parse(req.body); const date = parseDateOnly(input.date); if (date === null) { throw new HttpError( 400, ErrorCode.VALIDATION_ERROR, `Not a real calendar date: ${input.date}`, ); } const { id: viewerId, houseId } = res.locals.userProfile; const item = await addPlanningItem(houseId, viewerId, houseId, date, input); res.status(201).json(item); }), ); /** Removes one recipe from a planning slot. */ planningRouter.delete( "/items/:id", requireAuth, wrapAsyncHandler(async (req, res) => { const id = parsePlanningItemId(req.params.id); await removePlanningItem(id, res.locals.userProfile.houseId); res.status(204).end(); }), );