- Add gantt task CRUD bash and TypeScript utilities - Update MEMORY.md with CRUD capabilities and rules - Update daily memory with subagent completions - Document: full task links, attach-then-delete rule
183 lines
4.8 KiB
TypeScript
183 lines
4.8 KiB
TypeScript
/**
|
|
* Gantt Board Task CRUD Utilities
|
|
* Full CRUD operations on gantt board tasks via Supabase
|
|
*/
|
|
|
|
const SUPABASE_URL = "https://qnatchrjlpehiijwtreh.supabase.co";
|
|
const SERVICE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuYXRjaHJqbHBlaGlpand0cmVoIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MTY0MDQzNiwiZXhwIjoyMDg3MjE2NDM2fQ.rHoc3NfL59S4lejU4-ArSzox1krQkQG-TnfXb6sslm0";
|
|
|
|
const HEADERS = {
|
|
"apikey": SERVICE_KEY,
|
|
"Authorization": `Bearer ${SERVICE_KEY}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
/**
|
|
* Generate UUID v4
|
|
*/
|
|
function generateUUID(): string {
|
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
const r = (Math.random() * 16) | 0;
|
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get current ISO timestamp
|
|
*/
|
|
function nowISO(): string {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
/**
|
|
* Task interface
|
|
*/
|
|
export interface GanttTask {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
status: "open" | "todo" | "blocked" | "in-progress" | "review" | "validate" | "archived" | "canceled" | "done";
|
|
priority: "low" | "medium" | "high" | "urgent";
|
|
type?: "idea" | "task" | "bug" | "research" | "plan";
|
|
project_id: string;
|
|
sprint_id?: string;
|
|
assignee_id?: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
due_date?: string;
|
|
tags?: string[];
|
|
comments?: unknown[];
|
|
attachments?: unknown[];
|
|
}
|
|
|
|
/**
|
|
* LIST all tasks (optionally filtered by status)
|
|
*/
|
|
export async function listTasks(status?: string): Promise<GanttTask[]> {
|
|
let url = `${SUPABASE_URL}/rest/v1/tasks?select=*&order=created_at.desc`;
|
|
if (status) {
|
|
url = `${SUPABASE_URL}/rest/v1/tasks?select=*&status=eq.${status}&order=created_at.desc`;
|
|
}
|
|
|
|
const res = await fetch(url, { headers: HEADERS });
|
|
if (!res.ok) throw new Error(`Failed to list tasks: ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
/**
|
|
* GET a single task by ID
|
|
*/
|
|
export async function getTask(taskId: string): Promise<GanttTask | null> {
|
|
const url = `${SUPABASE_URL}/rest/v1/tasks?id=eq.${taskId}&select=*`;
|
|
const res = await fetch(url, { headers: HEADERS });
|
|
if (!res.ok) throw new Error(`Failed to get task: ${res.status}`);
|
|
const tasks = await res.json();
|
|
return tasks[0] || null;
|
|
}
|
|
|
|
/**
|
|
* CREATE a new task
|
|
*/
|
|
export async function createTask(params: {
|
|
title: string;
|
|
description?: string;
|
|
status?: GanttTask["status"];
|
|
priority?: GanttTask["priority"];
|
|
type?: GanttTask["type"];
|
|
projectId?: string;
|
|
assigneeId?: string;
|
|
dueDate?: string;
|
|
tags?: string[];
|
|
}): Promise<GanttTask> {
|
|
const task: Partial<GanttTask> = {
|
|
id: generateUUID(),
|
|
title: params.title,
|
|
description: params.description,
|
|
status: params.status || "open",
|
|
priority: params.priority || "medium",
|
|
type: params.type || "task",
|
|
project_id: params.projectId || "1",
|
|
assignee_id: params.assigneeId || "9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa", // Max
|
|
created_at: nowISO(),
|
|
updated_at: nowISO(),
|
|
due_date: params.dueDate,
|
|
tags: params.tags || [],
|
|
comments: [],
|
|
attachments: [],
|
|
};
|
|
|
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/tasks`, {
|
|
method: "POST",
|
|
headers: HEADERS,
|
|
body: JSON.stringify(task),
|
|
});
|
|
|
|
if (!res.ok) throw new Error(`Failed to create task: ${res.status}`);
|
|
return task as GanttTask;
|
|
}
|
|
|
|
/**
|
|
* UPDATE a task (partial update)
|
|
*/
|
|
export async function updateTask(
|
|
taskId: string,
|
|
updates: Partial<Omit<GanttTask, "id" | "created_at">>
|
|
): Promise<void> {
|
|
const payload = {
|
|
...updates,
|
|
updated_at: nowISO(),
|
|
};
|
|
|
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/tasks?id=eq.${taskId}`, {
|
|
method: "PATCH",
|
|
headers: HEADERS,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!res.ok) throw new Error(`Failed to update task: ${res.status}`);
|
|
}
|
|
|
|
/**
|
|
* DELETE a task
|
|
*/
|
|
export async function deleteTask(taskId: string): Promise<void> {
|
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/tasks?id=eq.${taskId}`, {
|
|
method: "DELETE",
|
|
headers: HEADERS,
|
|
});
|
|
|
|
if (!res.ok) throw new Error(`Failed to delete task: ${res.status}`);
|
|
}
|
|
|
|
/**
|
|
* Update task status (convenience method)
|
|
*/
|
|
export async function updateTaskStatus(
|
|
taskId: string,
|
|
status: GanttTask["status"]
|
|
): Promise<void> {
|
|
return updateTask(taskId, { status });
|
|
}
|
|
|
|
/**
|
|
* Assign task to user (convenience method)
|
|
*/
|
|
export async function assignTask(
|
|
taskId: string,
|
|
assigneeId: string
|
|
): Promise<void> {
|
|
return updateTask(taskId, { assignee_id: assigneeId });
|
|
}
|
|
|
|
/**
|
|
* Mark task as done (convenience method)
|
|
*/
|
|
export async function completeTask(taskId: string): Promise<void> {
|
|
return updateTask(taskId, { status: "done" });
|
|
}
|
|
|
|
// Example usage:
|
|
// const task = await createTask({ title: "New feature", priority: "high" });
|
|
// await updateTaskStatus(task.id, "in-progress");
|
|
// await completeTask(task.id);
|