133 lines
3.7 KiB
TypeScript
133 lines
3.7 KiB
TypeScript
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
import { Hono } from "hono";
|
|
import type { Database, Tables } from "./database.types.js";
|
|
import { supabaseMiddleware } from "./middleware.js";
|
|
import {
|
|
type EventTypeConfig,
|
|
type Exception,
|
|
generateTimeSlots,
|
|
getDateStringCET,
|
|
getDayOfWeek,
|
|
type TimeSlot,
|
|
type WeeklyAvailability,
|
|
} from "./slots.js";
|
|
|
|
export const publicRouter = new Hono<{
|
|
Variables: {
|
|
supabase: SupabaseClient;
|
|
};
|
|
}>();
|
|
|
|
publicRouter.use(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),
|
|
});
|
|
});
|