import type { SupabaseClient } from '@supabase/supabase-js'; import type { Database } from '$lib/supabase/types'; import { createLogger } from '$lib/utils/logger'; const log = createLogger('api.events'); export interface Event { id: string; org_id: string; name: string; slug: string; description: string | null; status: 'planning' | 'active' | 'completed' | 'archived'; start_date: string | null; end_date: string | null; venue_name: string | null; venue_address: string | null; cover_image_url: string | null; color: string | null; created_by: string | null; created_at: string; updated_at: string; } export interface EventMember { id: string; event_id: string; user_id: string; role: 'lead' | 'manager' | 'member'; assigned_at: string; } export interface EventWithCounts extends Event { member_count: number; } function slugify(text: string): string { return text .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/[\s_]+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') .slice(0, 60) || 'event'; } export async function fetchEvents( supabase: SupabaseClient, orgId: string, status?: string ): Promise { let query = supabase .from('events') .select('*, event_members(count)') .eq('org_id', orgId) .order('start_date', { ascending: true, nullsFirst: false }); if (status && status !== 'all') { query = query.eq('status', status); } const { data, error } = await query; if (error) { log.error('fetchEvents failed', { error, data: { orgId } }); throw error; } const events: EventWithCounts[] = (data ?? []).map((e: any) => ({ ...e, member_count: e.event_members?.[0]?.count ?? 0, event_members: undefined, })); log.debug('fetchEvents ok', { data: { count: events.length } }); return events; } export async function fetchEvent( supabase: SupabaseClient, eventId: string ): Promise { const { data, error } = await supabase .from('events') .select('*') .eq('id', eventId) .single(); if (error) { if (error.code === 'PGRST116') return null; log.error('fetchEvent failed', { error, data: { eventId } }); throw error; } return data as unknown as Event; } export async function fetchEventBySlug( supabase: SupabaseClient, orgId: string, eventSlug: string ): Promise { const { data, error } = await supabase .from('events') .select('*') .eq('org_id', orgId) .eq('slug', eventSlug) .single(); if (error) { if (error.code === 'PGRST116') return null; log.error('fetchEventBySlug failed', { error, data: { orgId, eventSlug } }); throw error; } return data as unknown as Event; } export async function createEvent( supabase: SupabaseClient, orgId: string, userId: string, params: { name: string; description?: string; start_date?: string; end_date?: string; venue_name?: string; venue_address?: string; color?: string; } ): Promise { const baseSlug = slugify(params.name); // Ensure unique slug within org const { data: existing } = await supabase .from('events') .select('slug') .eq('org_id', orgId) .like('slug', `${baseSlug}%`); let slug = baseSlug; if (existing && existing.length > 0) { const existingSlugs = new Set(existing.map((e: any) => e.slug)); if (existingSlugs.has(slug)) { let i = 2; while (existingSlugs.has(`${baseSlug}-${i}`)) i++; slug = `${baseSlug}-${i}`; } } const { data, error } = await supabase .from('events') .insert({ org_id: orgId, name: params.name, slug, description: params.description ?? null, start_date: params.start_date ?? null, end_date: params.end_date ?? null, venue_name: params.venue_name ?? null, venue_address: params.venue_address ?? null, color: params.color ?? null, created_by: userId, }) .select() .single(); if (error) { log.error('createEvent failed', { error, data: { orgId, name: params.name } }); throw error; } log.info('createEvent ok', { data: { id: data.id, name: params.name, slug } }); return data as unknown as Event; } export async function updateEvent( supabase: SupabaseClient, eventId: string, params: Partial> ): Promise { const { data, error } = await supabase .from('events') .update({ ...params, updated_at: new Date().toISOString() }) .eq('id', eventId) .select() .single(); if (error) { log.error('updateEvent failed', { error, data: { eventId } }); throw error; } log.info('updateEvent ok', { data: { id: data.id } }); return data as unknown as Event; } export async function deleteEvent( supabase: SupabaseClient, eventId: string ): Promise { const { error } = await supabase .from('events') .delete() .eq('id', eventId); if (error) { log.error('deleteEvent failed', { error, data: { eventId } }); throw error; } log.info('deleteEvent ok', { data: { eventId } }); } export async function fetchEventMembers( supabase: SupabaseClient, eventId: string ): Promise<(EventMember & { profile?: { id: string; email: string; full_name: string | null; avatar_url: string | null } })[]> { const { data: members, error } = await supabase .from('event_members') .select('*') .eq('event_id', eventId) .order('assigned_at'); if (error) { log.error('fetchEventMembers failed', { error, data: { eventId } }); throw error; } if (!members || members.length === 0) return []; // Fetch profiles separately (same pattern as org_members) const userIds = members.map((m: any) => m.user_id); const { data: profiles } = await supabase .from('profiles') .select('id, email, full_name, avatar_url') .in('id', userIds); const profileMap = Object.fromEntries((profiles ?? []).map(p => [p.id, p])); return members.map((m: any) => ({ ...m, profile: profileMap[m.user_id] ?? undefined, })); } export async function addEventMember( supabase: SupabaseClient, eventId: string, userId: string, role: 'lead' | 'manager' | 'member' = 'member' ): Promise { const { data, error } = await supabase .from('event_members') .upsert({ event_id: eventId, user_id: userId, role }, { onConflict: 'event_id,user_id' }) .select() .single(); if (error) { log.error('addEventMember failed', { error, data: { eventId, userId } }); throw error; } return data as unknown as EventMember; } export async function removeEventMember( supabase: SupabaseClient, eventId: string, userId: string ): Promise { const { error } = await supabase .from('event_members') .delete() .eq('event_id', eventId) .eq('user_id', userId); if (error) { log.error('removeEventMember failed', { error, data: { eventId, userId } }); throw error; } }