3e2e19a312
- Migration 0014: add customer_notes column to appointments - Schema update: add customerNotes field to appointments table - Factory update: include customerNotes in buildAppointment - Portal route: PATCH /api/portal/appointments/:id/notes - Ownership validation via impersonation session - Future-only validation (no edits after start) - 500 character limit - Register portal router in index.ts Co-Authored-By: Paperclip <noreply@paperclip.ing>
70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
import { Hono } from "hono";
|
|
import { zValidator } from "@hono/zod-validator";
|
|
import { z } from "zod";
|
|
import { and, eq, getDb, appointments, impersonationSessions } from "@groombook/db";
|
|
import type { AppEnv } from "../middleware/rbac.js";
|
|
|
|
export const portalRouter = new Hono<AppEnv>();
|
|
|
|
const customerNotesSchema = z.object({
|
|
customerNotes: z.string().max(500),
|
|
});
|
|
|
|
portalRouter.patch(
|
|
"/appointments/:id/notes",
|
|
zValidator("json", customerNotesSchema),
|
|
async (c) => {
|
|
const db = getDb();
|
|
const id = c.req.param("id");
|
|
const body = c.req.valid("json");
|
|
|
|
const sessionId = c.req.header("X-Impersonation-Session-Id");
|
|
if (!sessionId) {
|
|
return c.json({ error: "Unauthorized" }, 401);
|
|
}
|
|
|
|
const [session] = await db
|
|
.select()
|
|
.from(impersonationSessions)
|
|
.where(
|
|
and(
|
|
eq(impersonationSessions.id, sessionId),
|
|
eq(impersonationSessions.status, "active")
|
|
)
|
|
)
|
|
.limit(1);
|
|
|
|
if (!session || session.expiresAt <= new Date()) {
|
|
return c.json({ error: "Unauthorized" }, 401);
|
|
}
|
|
|
|
const authClientId = session.clientId;
|
|
|
|
const [appt] = await db
|
|
.select()
|
|
.from(appointments)
|
|
.where(eq(appointments.id, id))
|
|
.limit(1);
|
|
|
|
if (!appt) {
|
|
return c.json({ error: "Not found" }, 404);
|
|
}
|
|
|
|
if (appt.clientId !== authClientId) {
|
|
return c.json({ error: "Forbidden" }, 403);
|
|
}
|
|
|
|
if (appt.startTime <= new Date()) {
|
|
return c.json({ error: "Cannot edit notes for past or in-progress appointments" }, 422);
|
|
}
|
|
|
|
const [updated] = await db
|
|
.update(appointments)
|
|
.set({ customerNotes: body.customerNotes, updatedAt: new Date() })
|
|
.where(eq(appointments.id, id))
|
|
.returning();
|
|
|
|
return c.json(updated);
|
|
}
|
|
);
|