import { Hono } from "hono"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod/v3"; import { and, eq, isNull, getDb, bufferRules, services, } from "@groombook/db"; import type { AppEnv } from "../middleware/rbac.js"; import { requireRole } from "../middleware/rbac.js"; export const bufferRulesRouter = new Hono(); // Apply manager role guard to all routes bufferRulesRouter.use("*", requireRole("manager")); const createBufferRuleSchema = z.object({ serviceId: z.string().uuid(), sizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(), coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(), bufferMinutes: z.number().int().positive(), }); const updateBufferRuleSchema = z.object({ bufferMinutes: z.number().int().positive(), }); bufferRulesRouter.get("/", async (c) => { const db = getDb(); const serviceId = c.req.query("serviceId"); const conditions = []; if (serviceId) conditions.push(eq(bufferRules.serviceId, serviceId)); const rows = await db .select({ id: bufferRules.id, serviceId: bufferRules.serviceId, serviceName: services.name, sizeCategory: bufferRules.sizeCategory, coatType: bufferRules.coatType, bufferMinutes: bufferRules.bufferMinutes, createdAt: bufferRules.createdAt, updatedAt: bufferRules.updatedAt, }) .from(bufferRules) .leftJoin(services, eq(bufferRules.serviceId, services.id)) .where(conditions.length > 0 ? and(...conditions) : undefined); return c.json(rows); }); bufferRulesRouter.post( "/", zValidator("json", createBufferRuleSchema), async (c) => { const db = getDb(); const body = c.req.valid("json"); // Validate serviceId exists const [svc] = await db .select({ id: services.id }) .from(services) .where(eq(services.id, body.serviceId)) .limit(1); if (!svc) return c.json({ error: "Service not found" }, 404); // Check for duplicate — sizeCategory/coatType are nullable, use isNull for null check const conditions = [eq(bufferRules.serviceId, body.serviceId)]; if (body.sizeCategory) { conditions.push(eq(bufferRules.sizeCategory, body.sizeCategory)); } else { conditions.push(isNull(bufferRules.sizeCategory)); } if (body.coatType) { conditions.push(eq(bufferRules.coatType, body.coatType)); } else { conditions.push(isNull(bufferRules.coatType)); } const [existing] = await db .select({ id: bufferRules.id }) .from(bufferRules) .where(and(...conditions)) .limit(1); if (existing) return c.json({ error: "Duplicate rule for this service and attributes" }, 409); const [row] = await db .insert(bufferRules) .values({ serviceId: body.serviceId, sizeCategory: body.sizeCategory, coatType: body.coatType, bufferMinutes: body.bufferMinutes, }) .returning(); return c.json(row, 201); } ); bufferRulesRouter.patch( "/:id", zValidator("json", updateBufferRuleSchema), async (c) => { const db = getDb(); const body = c.req.valid("json"); const [row] = await db .update(bufferRules) .set({ bufferMinutes: body.bufferMinutes, updatedAt: new Date() }) .where(eq(bufferRules.id, c.req.param("id"))) .returning(); if (!row) return c.json({ error: "Not found" }, 404); return c.json(row); } ); bufferRulesRouter.delete("/:id", async (c) => { const db = getDb(); const [row] = await db .delete(bufferRules) .where(eq(bufferRules.id, c.req.param("id"))) .returning(); if (!row) return c.json({ error: "Not found" }, 404); return c.json({ ok: true }); });