import { createServerFn } from '@tanstack/react-start' import { and, eq, ne } from 'drizzle-orm' import { z } from 'zod' import { db } from '#/db' import { reservations, restaurantTables } from '#/db/schema' import { requireStaffSession } from '#/lib/auth' export const getTables = createServerFn({ method: 'GET' }).handler(async () => { return db.select().from(restaurantTables).orderBy(restaurantTables.name) }) export const getReservations = createServerFn({ method: 'GET' }).handler(async () => { await requireStaffSession() return db .select({ id: reservations.id, tableId: reservations.tableId, tableName: restaurantTables.name, customerName: reservations.customerName, customerEmail: reservations.customerEmail, partySize: reservations.partySize, reservationTime: reservations.reservationTime, }) .from(reservations) .innerJoin(restaurantTables, eq(reservations.tableId, restaurantTables.id)) .orderBy(reservations.reservationTime) }) const createReservationSchema = z.object({ tableId: z.number().int().positive(), customerName: z.string().trim().min(1, 'Name is required').max(120), customerEmail: z.string().trim().email('Enter a valid email'), partySize: z.number().int().min(1, 'At least 1 guest').max(20), reservationTime: z.string().refine((value) => !Number.isNaN(Date.parse(value)), { message: 'Enter a valid date and time', }), }) export const createReservation = createServerFn({ method: 'POST' }) .validator(createReservationSchema) .handler(async ({ data }) => { const reservationTime = new Date(data.reservationTime) if (reservationTime.getTime() < Date.now()) { throw new Error('Reservation time must be in the future') } const [table] = await db .select() .from(restaurantTables) .where(eq(restaurantTables.id, data.tableId)) if (!table) { throw new Error('Selected table does not exist') } if (data.partySize > table.seats) { throw new Error(`${table.name} only seats up to ${table.seats} guests`) } const [conflict] = await db .select() .from(reservations) .where( and( eq(reservations.tableId, data.tableId), eq(reservations.reservationTime, reservationTime), ), ) if (conflict) { throw new Error('This table is already booked for that time') } const [reservation] = await db .insert(reservations) .values({ tableId: data.tableId, customerName: data.customerName, customerEmail: data.customerEmail, partySize: data.partySize, reservationTime, }) .returning() return reservation }) const updateReservationSchema = createReservationSchema.extend({ id: z.number().int().positive(), }) export const updateReservation = createServerFn({ method: 'POST' }) .validator(updateReservationSchema) .handler(async ({ data }) => { await requireStaffSession() const reservationTime = new Date(data.reservationTime) const [table] = await db .select() .from(restaurantTables) .where(eq(restaurantTables.id, data.tableId)) if (!table) { throw new Error('Selected table does not exist') } if (data.partySize > table.seats) { throw new Error(`${table.name} only seats up to ${table.seats} guests`) } const [conflict] = await db .select() .from(reservations) .where( and( eq(reservations.tableId, data.tableId), eq(reservations.reservationTime, reservationTime), ne(reservations.id, data.id), ), ) if (conflict) { throw new Error('This table is already booked for that time') } const [updated] = await db .update(reservations) .set({ tableId: data.tableId, customerName: data.customerName, customerEmail: data.customerEmail, partySize: data.partySize, reservationTime, }) .where(eq(reservations.id, data.id)) .returning() if (!updated) { throw new Error('Reservation not found') } return updated }) const deleteReservationSchema = z.object({ id: z.number().int().positive(), }) export const deleteReservation = createServerFn({ method: 'POST' }) .validator(deleteReservationSchema) .handler(async ({ data }) => { await requireStaffSession() const [deleted] = await db.delete(reservations).where(eq(reservations.id, data.id)).returning() if (!deleted) { throw new Error('Reservation not found') } return deleted })