import type { SupabaseClient } from "@supabase/supabase-js"; import { Hono } from "hono"; import type { Database, Tables } from "./database.types.js"; import type { Middlewares } from "./middleware.js"; import { type EventTypeConfig, type Exception, generateTimeSlots, getDateStringCET, getDayOfWeek, type TimeSlot, type WeeklyAvailability, } from "./slots.js"; export const getPublicRouter = (middlewares: Middlewares) => { const publicRouter = new Hono<{ Variables: { supabase: SupabaseClient; }; }>(); publicRouter.use(middlewares.supabaseMiddleware); publicRouter.get("/slots/:shortUserId/:standardName", async (c) => { const supabase = c.get("supabase"); const shortUserId = c.req.param("shortUserId"); const standardName = c.req.param("standardName"); // Get user const { data: userData, error: userError } = await supabase .from("profiles") .select("*") .eq("short_user_id", shortUserId) .single(); if (userError || !userData) { return c.json({ error: "User not found" }, 404); } const user = userData as Tables<"profiles">; // Get event type const { data: eventTypeData, error: eventTypeError } = await supabase .from("event_types") .select("*") .eq("user_id", user.id) .eq("standard_name", standardName) .is("deleted_at", null) .single(); if (eventTypeError || !eventTypeData) { return c.json({ error: "Event type not found" }, 404); } const eventType = eventTypeData as Database["public"]["Tables"]["event_types"]["Row"]; const eventTypeConfig = eventType.config as EventTypeConfig; // Get user's availabilities const { data: availabilitiesData, error: availabilitiesError } = await supabase .from("availabilities") .select("*") .eq("user_id", user.id) .single(); if (availabilitiesError) { return c.json({ error: "Availabilities not found" }, 404); } const availabilities = availabilitiesData as Tables<"availabilities">; const weeklyAvailability = availabilities.availability_data as WeeklyAvailability; const exceptions = (availabilities.exceptions as Exception[]) || []; // Get existing events for the next month // Use CET time for availability calculations const now = new Date(); const nextMonth = new Date(now); nextMonth.setMonth(now.getMonth() + 2); const { data: eventsData, error: eventsError } = await supabase .from("events") .select("*") .eq("created_by", user.id) .gte("start_date", getDateStringCET(now)) .lte("start_date", getDateStringCET(nextMonth)) .is("deleted_at", null); if (eventsError) { return c.json({ error: "Failed to fetch events" }, 500); } const existingEvents = eventsData as Tables<"events">[]; // Generate slots for the next month const slots: TimeSlot[] = []; const currentDate = new Date(now); while (currentDate <= nextMonth) { const dayOfWeek = getDayOfWeek(currentDate); const dayAvailability = weeklyAvailability[dayOfWeek]; if (dayAvailability) { const daySlots = generateTimeSlots( now, // Pass CET current time as first parameter currentDate, dayAvailability, eventTypeConfig, exceptions, existingEvents ); slots.push(...daySlots); } currentDate.setDate(currentDate.getDate() + 1); } // Group slots by date for easier frontend consumption const slotsByDate: { [date: string]: TimeSlot[] } = {}; slots.forEach((slot) => { if (!slotsByDate[slot.date]) { slotsByDate[slot.date] = []; } slotsByDate[slot.date].push(slot); }); return c.json({ user: { name: user.name, avatar_url: user.avatar_url }, eventType: eventTypeConfig, slots: slotsByDate, availableSlots: slots.filter((slot) => slot.available), }); }); return publicRouter; };