xtablo-source/api/src/public.ts
2025-11-04 10:53:31 +01:00

134 lines
3.9 KiB
TypeScript

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;
};