Files
api/src/routes/services.ts
T
Flea Flicker 07eb611549 feat(GRO-1427): add buffer rules CRUD — enums, table, routes
Re-implement lost commit from worktree cleanup. PR #12 already has
UAT_PLAYBOOK + factories fix; add all missing core implementation:
- Add petSizeCategoryEnum/coatTypeEnum to schema
- Add bufferRules table with service FK + unique constraint
- Add defaultBufferMinutes column to services table
- Change pets.coatType/petSizeCategory text columns to use enums
- Add routes/buffer-rules.ts: GET/POST/PATCH/DELETE, manager role guard
- Register /api/buffer-rules in index.ts
- Update services.ts PATCH to accept defaultBufferMinutes
- Update pets.ts POST/PATCH to accept sizeCategory/coatType
- Cast coatType/petSizeCategory in book.ts insert to match new enums
- Add 0031_buffer_rules.sql migration
- Fix factories.ts buildService to include defaultBufferMinutes: null

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 10:01:40 +00:00

75 lines
2.1 KiB
TypeScript

import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3";
import { eq, getDb, services } from "@groombook/db";
export const servicesRouter = new Hono();
const createServiceSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().max(2000).optional(),
basePriceCents: z.number().int().positive(),
durationMinutes: z.number().int().positive().max(480),
defaultBufferMinutes: z.number().int().min(0).optional(),
active: z.boolean().default(true),
});
const updateServiceSchema = createServiceSchema.partial();
servicesRouter.get("/", async (c) => {
const db = getDb();
const includeInactive = c.req.query("includeInactive") === "true";
const query = db.select().from(services).orderBy(services.name);
const rows = includeInactive
? await query
: await query.where(eq(services.active, true));
return c.json(rows);
});
servicesRouter.get("/:id", async (c) => {
const db = getDb();
const [row] = await db
.select()
.from(services)
.where(eq(services.id, c.req.param("id")));
if (!row) return c.json({ error: "Not found" }, 404);
return c.json(row);
});
servicesRouter.post(
"/",
zValidator("json", createServiceSchema),
async (c) => {
const db = getDb();
const body = c.req.valid("json");
const [row] = await db.insert(services).values(body).returning();
return c.json(row, 201);
}
);
servicesRouter.patch(
"/:id",
zValidator("json", updateServiceSchema),
async (c) => {
const db = getDb();
const body = c.req.valid("json");
const [row] = await db
.update(services)
.set({ ...body, updatedAt: new Date() })
.where(eq(services.id, c.req.param("id")))
.returning();
if (!row) return c.json({ error: "Not found" }, 404);
return c.json(row);
}
);
servicesRouter.delete("/:id", async (c) => {
const db = getDb();
const [row] = await db
.delete(services)
.where(eq(services.id, c.req.param("id")))
.returning();
if (!row) return c.json({ error: "Not found" }, 404);
return c.json({ ok: true });
});