e1e13d5091
Adds Zod validation across 5 API routes: 1. invoices GET / — query param validation (uuid, enum, int bounds) 2. book POST / — future-time refinement on startTime 3. appointments — recurrence series capped at 1 year 4. services — durationMinutes capped at 480 (8 hours) 5. stripe-webhooks — UUID validation on invoice IDs before DB lookup Closes GRO-636 Co-Authored-By: Paperclip <noreply@paperclip.ing>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 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),
|
|
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 });
|
|
});
|