import { call, os } from "@orpc/server"; import { z } from "zod"; import { randomUUID } from "crypto"; import { createKV } from "@/server/lib/create-kv"; const AvailabilitySchema = z.object({ id: z.string(), date: z.string(), // YYYY-MM-DD time: z.string(), // HH:MM durationMinutes: z.number().int().positive(), status: z.enum(["free", "reserved"]), reservedByBookingId: z.string().optional(), createdAt: z.string(), }); export type Availability = z.output; const kv = createKV("availability"); // Minimal Owner-Prüfung über Sessions/Users KV type Session = { id: string; userId: string; expiresAt: string; createdAt: string }; type User = { id: string; username: string; email: string; passwordHash: string; role: "customer" | "owner"; createdAt: string }; const sessionsKV = createKV("sessions"); const usersKV = createKV("users"); async function assertOwner(sessionId: string): Promise { const session = await sessionsKV.getItem(sessionId); if (!session) throw new Error("Invalid session"); if (new Date(session.expiresAt) < new Date()) throw new Error("Session expired"); const user = await usersKV.getItem(session.userId); if (!user || user.role !== "owner") throw new Error("Forbidden"); } const create = os .input( z.object({ sessionId: z.string(), date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), time: z.string().regex(/^\d{2}:\d{2}$/), durationMinutes: z.number().int().positive(), }) ) .handler(async ({ input }) => { await assertOwner(input.sessionId); const id = randomUUID(); const slot: Availability = { id, date: input.date, time: input.time, durationMinutes: input.durationMinutes, status: "free", createdAt: new Date().toISOString(), }; await kv.setItem(id, slot); return slot; }); const update = os .input(AvailabilitySchema.extend({ sessionId: z.string() })) .handler(async ({ input }) => { await assertOwner(input.sessionId); const { sessionId, ...rest } = input as any; await kv.setItem(rest.id, rest as Availability); return rest as Availability; }); const remove = os .input(z.object({ sessionId: z.string(), id: z.string() })) .handler(async ({ input }) => { await assertOwner(input.sessionId); const slot = await kv.getItem(input.id); if (slot && slot.status === "reserved") throw new Error("Cannot delete reserved slot"); await kv.removeItem(input.id); }); const list = os.handler(async () => { return kv.getAllItems(); }); const get = os.input(z.string()).handler(async ({ input }) => { return kv.getItem(input); }); const getByDate = os .input(z.string()) // YYYY-MM-DD .handler(async ({ input }) => { const all = await kv.getAllItems(); return all.filter((s) => s.date === input); }); const live = { list: os.handler(async function* ({ signal }) { yield call(list, {}, { signal }); for await (const _ of kv.subscribe()) { yield call(list, {}, { signal }); } }), byDate: os .input(z.string()) .handler(async function* ({ input, signal }) { yield call(getByDate, input, { signal }); for await (const _ of kv.subscribe()) { yield call(getByDate, input, { signal }); } }), }; export const router = { create, update, remove, list, get, getByDate, live, };