44da26820b
- Add buffer_rules table with serviceId/sizeCategory/coatType/bufferMinutes
- Add petSizeCategoryEnum (small/medium/large/extra_large) and coatTypeEnum
to schema; update pets table columns to use the typed enums
- Add defaultBufferMinutes to services table
- Add apps/api/src/routes/buffer-rules.ts with GET/POST/PATCH/DELETE,
all manager-only via requireRole("manager")
- Register /api/buffer-rules router in index.ts
- PATCH /api/services/:id accepts optional defaultBufferMinutes
- POST/PATCH /api/pets accepts optional sizeCategory and coatType
Co-Authored-By: Paperclip <noreply@paperclip.ing>
124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
import { Hono } from "hono";
|
|
import { zValidator } from "@hono/zod-validator";
|
|
import { z } from "zod/v3";
|
|
import { and, eq, getDb, isNull } from "../db/index.js";
|
|
import type { AppEnv } from "../middleware/rbac.js";
|
|
import { bufferRules, services } from "../db/index.js";
|
|
|
|
export const bufferRulesRouter = new Hono<AppEnv>();
|
|
|
|
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(),
|
|
});
|
|
|
|
// GET / — list all buffer rules, optionally filtered by serviceId
|
|
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,
|
|
sizeCategory: bufferRules.sizeCategory,
|
|
coatType: bufferRules.coatType,
|
|
bufferMinutes: bufferRules.bufferMinutes,
|
|
createdAt: bufferRules.createdAt,
|
|
updatedAt: bufferRules.updatedAt,
|
|
serviceName: services.name,
|
|
})
|
|
.from(bufferRules)
|
|
.innerJoin(services, eq(bufferRules.serviceId, services.id))
|
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
.orderBy(bufferRules.createdAt);
|
|
|
|
return c.json(rows);
|
|
});
|
|
|
|
// POST / — create a buffer rule
|
|
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));
|
|
if (!svc) return c.json({ error: "Service not found" }, 404);
|
|
|
|
// Check for duplicate (service + size + coat)
|
|
const [existing] = await db
|
|
.select({ id: bufferRules.id })
|
|
.from(bufferRules)
|
|
.where(
|
|
and(
|
|
eq(bufferRules.serviceId, body.serviceId),
|
|
body.sizeCategory !== undefined
|
|
? eq(bufferRules.sizeCategory, body.sizeCategory)
|
|
: isNull(bufferRules.sizeCategory),
|
|
body.coatType !== undefined
|
|
? eq(bufferRules.coatType, body.coatType)
|
|
: isNull(bufferRules.coatType)
|
|
)
|
|
);
|
|
if (existing) return c.json({ error: "Duplicate rule for this service+size+coat combination" }, 409);
|
|
|
|
const [row] = await db
|
|
.insert(bufferRules)
|
|
.values({
|
|
serviceId: body.serviceId,
|
|
sizeCategory: body.sizeCategory ?? null,
|
|
coatType: body.coatType ?? null,
|
|
bufferMinutes: body.bufferMinutes,
|
|
})
|
|
.returning();
|
|
|
|
return c.json(row, 201);
|
|
}
|
|
);
|
|
|
|
// PATCH /:id — update bufferMinutes only
|
|
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);
|
|
}
|
|
);
|
|
|
|
// DELETE /:id — delete a buffer rule
|
|
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 });
|
|
}); |