Mega push vol 6, started adding many awesome stuff, chat broken rn
This commit is contained in:
266
src/lib/api/event-tasks.ts
Normal file
266
src/lib/api/event-tasks.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { Database, EventTaskColumn, EventTask } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.event-tasks');
|
||||
|
||||
export interface TaskColumnWithTasks extends EventTaskColumn {
|
||||
cards: EventTask[];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Columns
|
||||
// ============================================================
|
||||
|
||||
export async function fetchTaskColumns(
|
||||
supabase: SupabaseClient<Database>,
|
||||
eventId: string
|
||||
): Promise<TaskColumnWithTasks[]> {
|
||||
const { data: columns, error: colErr } = await supabase
|
||||
.from('event_task_columns')
|
||||
.select('*')
|
||||
.eq('event_id', eventId)
|
||||
.order('position');
|
||||
|
||||
if (colErr) {
|
||||
log.error('Failed to fetch task columns', { error: colErr, data: { eventId } });
|
||||
throw colErr;
|
||||
}
|
||||
|
||||
const { data: tasks, error: taskErr } = await supabase
|
||||
.from('event_tasks')
|
||||
.select('*')
|
||||
.eq('event_id', eventId)
|
||||
.order('position');
|
||||
|
||||
if (taskErr) {
|
||||
log.error('Failed to fetch tasks', { error: taskErr, data: { eventId } });
|
||||
throw taskErr;
|
||||
}
|
||||
|
||||
const tasksByColumn = new Map<string, EventTask[]>();
|
||||
for (const task of tasks ?? []) {
|
||||
const arr = tasksByColumn.get(task.column_id) ?? [];
|
||||
arr.push(task);
|
||||
tasksByColumn.set(task.column_id, arr);
|
||||
}
|
||||
|
||||
return (columns ?? []).map((col) => ({
|
||||
...col,
|
||||
cards: tasksByColumn.get(col.id) ?? [],
|
||||
}));
|
||||
}
|
||||
|
||||
export async function createTaskColumn(
|
||||
supabase: SupabaseClient<Database>,
|
||||
eventId: string,
|
||||
name: string,
|
||||
position?: number
|
||||
): Promise<EventTaskColumn> {
|
||||
if (position === undefined) {
|
||||
const { count } = await supabase
|
||||
.from('event_task_columns')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('event_id', eventId);
|
||||
position = count ?? 0;
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('event_task_columns')
|
||||
.insert({ event_id: eventId, name, position })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
log.error('Failed to create task column', { error, data: { eventId, name } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function renameTaskColumn(
|
||||
supabase: SupabaseClient<Database>,
|
||||
columnId: string,
|
||||
name: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('event_task_columns')
|
||||
.update({ name })
|
||||
.eq('id', columnId);
|
||||
|
||||
if (error) {
|
||||
log.error('Failed to rename task column', { error, data: { columnId, name } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteTaskColumn(
|
||||
supabase: SupabaseClient<Database>,
|
||||
columnId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('event_task_columns')
|
||||
.delete()
|
||||
.eq('id', columnId);
|
||||
|
||||
if (error) {
|
||||
log.error('Failed to delete task column', { error, data: { columnId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tasks
|
||||
// ============================================================
|
||||
|
||||
export async function createTask(
|
||||
supabase: SupabaseClient<Database>,
|
||||
eventId: string,
|
||||
columnId: string,
|
||||
title: string,
|
||||
createdBy?: string
|
||||
): Promise<EventTask> {
|
||||
const { count } = await supabase
|
||||
.from('event_tasks')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('column_id', columnId);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('event_tasks')
|
||||
.insert({
|
||||
event_id: eventId,
|
||||
column_id: columnId,
|
||||
title,
|
||||
position: count ?? 0,
|
||||
created_by: createdBy ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
log.error('Failed to create task', { error, data: { eventId, columnId, title } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateTask(
|
||||
supabase: SupabaseClient<Database>,
|
||||
taskId: string,
|
||||
updates: Partial<Pick<EventTask, 'title' | 'description' | 'priority' | 'due_date' | 'color' | 'assignee_id'>>
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('event_tasks')
|
||||
.update({ ...updates, updated_at: new Date().toISOString() })
|
||||
.eq('id', taskId);
|
||||
|
||||
if (error) {
|
||||
log.error('Failed to update task', { error, data: { taskId, updates } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteTask(
|
||||
supabase: SupabaseClient<Database>,
|
||||
taskId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('event_tasks')
|
||||
.delete()
|
||||
.eq('id', taskId);
|
||||
|
||||
if (error) {
|
||||
log.error('Failed to delete task', { error, data: { taskId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function moveTask(
|
||||
supabase: SupabaseClient<Database>,
|
||||
taskId: string,
|
||||
newColumnId: string,
|
||||
newPosition: number
|
||||
): Promise<void> {
|
||||
// Fetch all tasks in the target column
|
||||
const { data: colTasks, error: fetchErr } = await supabase
|
||||
.from('event_tasks')
|
||||
.select('id, position')
|
||||
.eq('column_id', newColumnId)
|
||||
.order('position');
|
||||
|
||||
if (fetchErr) {
|
||||
log.error('Failed to fetch column tasks for reorder', { error: fetchErr });
|
||||
throw fetchErr;
|
||||
}
|
||||
|
||||
// Build the new order
|
||||
const existing = (colTasks ?? []).filter((t) => t.id !== taskId);
|
||||
existing.splice(newPosition, 0, { id: taskId, position: newPosition });
|
||||
|
||||
// Update positions + column for changed tasks
|
||||
const updates = existing
|
||||
.map((t, i) => ({ id: t.id, position: i, column_id: newColumnId }))
|
||||
.filter((t, i) => {
|
||||
const orig = colTasks?.find((c) => c.id === t.id);
|
||||
return !orig || orig.position !== i || t.id === taskId;
|
||||
});
|
||||
|
||||
if (updates.length > 0) {
|
||||
await Promise.all(
|
||||
updates.map((u) =>
|
||||
supabase
|
||||
.from('event_tasks')
|
||||
.update({ column_id: u.column_id, position: u.position, updated_at: new Date().toISOString() })
|
||||
.eq('id', u.id)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Realtime
|
||||
// ============================================================
|
||||
|
||||
export interface RealtimeChangePayload<T = Record<string, unknown>> {
|
||||
event: 'INSERT' | 'UPDATE' | 'DELETE';
|
||||
new: T;
|
||||
old: Partial<T>;
|
||||
}
|
||||
|
||||
export function subscribeToEventTasks(
|
||||
supabase: SupabaseClient<Database>,
|
||||
eventId: string,
|
||||
columnIds: string[],
|
||||
onColumnChange: (payload: RealtimeChangePayload<EventTaskColumn>) => void,
|
||||
onTaskChange: (payload: RealtimeChangePayload<EventTask>) => void
|
||||
) {
|
||||
const channel = supabase.channel(`event-tasks:${eventId}`);
|
||||
const columnIdSet = new Set(columnIds);
|
||||
|
||||
channel
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'event_task_columns', filter: `event_id=eq.${eventId}` },
|
||||
(payload) => onColumnChange({
|
||||
event: payload.eventType as 'INSERT' | 'UPDATE' | 'DELETE',
|
||||
new: payload.new as EventTaskColumn,
|
||||
old: payload.old as Partial<EventTaskColumn>,
|
||||
})
|
||||
)
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'event_tasks', filter: `event_id=eq.${eventId}` },
|
||||
(payload) => {
|
||||
const task = (payload.new ?? payload.old) as Partial<EventTask>;
|
||||
const colId = task.column_id ?? (payload.old as Partial<EventTask>)?.column_id;
|
||||
if (colId && !columnIdSet.has(colId)) return;
|
||||
|
||||
onTaskChange({
|
||||
event: payload.eventType as 'INSERT' | 'UPDATE' | 'DELETE',
|
||||
new: payload.new as EventTask,
|
||||
old: payload.old as Partial<EventTask>,
|
||||
});
|
||||
}
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return channel;
|
||||
}
|
||||
Reference in New Issue
Block a user