import type { FastifyInstance } from "fastify"; import { eq, siteConfig, type Db } from "@parking/db"; import { requireRole } from "../auth.js"; import { getOccupancy } from "../occupancy.js"; // Site config (capacity) + live occupancy. Occupancy is a fold over the signed // ledger; capacity is an admin-set knob. The FULL gate (refuse transient entry at // capacity) lives in the entry flow. See wiki/concepts/capacity-occupancy.md. interface SiteConfigBody { /** Nominal capacity; null = no limit. */ capacity: number | null; } export async function siteRoutes(app: FastifyInstance, db: Db): Promise { const readGuard = requireRole("admin", "operator", "cashier", "readonly"); const writeGuard = requireRole("admin"); // Live occupancy: cars inside, capacity, free, full. Any signed-in role. app.get("/api/occupancy", { preHandler: readGuard }, async () => getOccupancy(db)); // Read site config (capacity). app.get("/api/site-config", { preHandler: readGuard }, async () => { const row = db.select().from(siteConfig).where(eq(siteConfig.id, 1)).get(); return { capacity: row?.capacity ?? null }; }); // Set capacity (admin). null or 0+ integer. app.put<{ Body: SiteConfigBody }>("/api/site-config", { preHandler: writeGuard }, async (req, reply) => { const { capacity } = req.body ?? ({} as SiteConfigBody); if (capacity != null && (!Number.isInteger(capacity) || capacity < 0)) { return reply.code(400).send({ error: "capacity must be a non-negative integer or null" }); } const existing = db.select().from(siteConfig).where(eq(siteConfig.id, 1)).get(); const updatedAt = new Date().toISOString(); if (existing) { db.update(siteConfig).set({ capacity: capacity ?? null, updatedAt }).where(eq(siteConfig.id, 1)).run(); } else { db.insert(siteConfig).values({ id: 1, capacity: capacity ?? null, updatedAt }).run(); } return { capacity: capacity ?? null }; }); }