import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; import { ErrorCode, createHouseSchema, joinHouseSchema, renameHouseSchema, updateHouseSourcesSchema, } from "@batch-cooking/shared"; import { Router } from "express"; import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js"; import { createHouse, deleteHouse, getCurrentHouse, getHouseSourceIds, joinHouse, leaveCurrentHouse, removeMember, renameHouse, updateHouseSources, } from "./house.service.js"; /** Router mounted at `/house` in app.ts. Every route requires a session — a household is per-user (via their profile), never public. */ export const houseRouter = Router(); houseRouter.get( "/current", requireAuth, wrapAsyncHandler(async (_req, res) => { const house = await getCurrentHouse(res.locals.userProfile.houseId); res.status(200).json(house); }), ); /** The household step of the profile journey (onboarding wizard and the `/parametres/foyer` settings page both call this) — renaming, open to any member. */ houseRouter.patch( "/current", requireAuth, wrapAsyncHandler(async (req, res) => { const input = renameHouseSchema.parse(req.body); const house = await renameHouse(res.locals.userProfile.houseId, input.name); res.status(200).json(house); }), ); /** Creates a new household for a profile that doesn't have one yet — the "create" half of the optional household step. */ houseRouter.post( "/", requireAuth, wrapAsyncHandler(async (req, res) => { const input = createHouseSchema.parse(req.body); const house = await createHouse( res.locals.userProfile.id, res.locals.userProfile.houseId, input.name, ); res.status(201).json(house); }), ); /** Joins an existing household by invite code — the "join" half of the optional household step. */ houseRouter.post( "/join", requireAuth, wrapAsyncHandler(async (req, res) => { const input = joinHouseSchema.parse(req.body); const house = await joinHouse( res.locals.userProfile.id, res.locals.userProfile.houseId, input.inviteCode, ); res.status(200).json(house); }), ); /** Removes the caller from their own household — see `deleteHouse` below for removing the household itself. */ houseRouter.post( "/leave", requireAuth, wrapAsyncHandler(async (_req, res) => { await leaveCurrentHouse(res.locals.userProfile.id, res.locals.userProfile.houseId); res.status(204).end(); }), ); /** Deletes the household entirely — every member loses it. Admin-only, see `house.service.ts`. */ houseRouter.delete( "/current", requireAuth, wrapAsyncHandler(async (_req, res) => { await deleteHouse(res.locals.userProfile.id, res.locals.userProfile.houseId); res.status(204).end(); }), ); /** Which recipe sources the household currently sees in its recipe tabs — the source step of the onboarding wizard and the `/parametres/foyer` settings page both call this. */ houseRouter.get( "/current/sources", requireAuth, wrapAsyncHandler(async (_req, res) => { const sourceIds = await getHouseSourceIds(res.locals.userProfile.houseId); res.status(200).json(sourceIds); }), ); /** Replaces the household's enabled-source set — same callers as the GET above. */ houseRouter.patch( "/current/sources", requireAuth, wrapAsyncHandler(async (req, res) => { const input = updateHouseSourcesSchema.parse(req.body); const sourceIds = await updateHouseSources(res.locals.userProfile.houseId, input.sourceIds); res.status(200).json(sourceIds); }), ); /** Removes one specific member from the caller's household. Admin-only, see `house.service.ts`. */ houseRouter.delete( "/members/:memberId", requireAuth, wrapAsyncHandler(async (req, res) => { const memberId = Number(req.params.memberId); if (!Number.isInteger(memberId)) { throw new HttpError(400, ErrorCode.VALIDATION_ERROR, "memberId must be an integer"); } const house = await removeMember( res.locals.userProfile.id, res.locals.userProfile.houseId, memberId, ); res.status(200).json(house); }), );