diff --git a/client/lib/aethex-collab-service.ts b/client/lib/aethex-collab-service.ts new file mode 100644 index 00000000..b0a45df9 --- /dev/null +++ b/client/lib/aethex-collab-service.ts @@ -0,0 +1,97 @@ +import { supabase } from "@/lib/supabase"; + +export type TeamVisibility = "public" | "private"; +export type MembershipRole = "owner" | "admin" | "member"; +export type ProjectRole = "owner" | "manager" | "contributor" | "viewer"; +export type TaskStatus = "todo" | "doing" | "done" | "blocked"; + +export const aethexCollabService = { + // Teams + async listMyTeams(userId: string) { + const { data, error } = await supabase + .from("team_memberships") + .select("team_id, teams:team_id ( id, name, slug, description, visibility, created_at )") + .eq("user_id", userId) + .order("created_at", { ascending: false }); + if (error) return [] as any[]; + return (data || []) as any[]; + }, + + async createTeam(ownerId: string, name: string, description?: string | null, visibility: TeamVisibility = "private") { + const { data, error } = await supabase + .from("teams") + .insert({ owner_id: ownerId, name, description: description || null, visibility }) + .select() + .single(); + if (error) throw new Error(error.message || "Unable to create team"); + + const team = data as any; + await supabase + .from("team_memberships") + .insert({ team_id: team.id, user_id: ownerId, role: "owner" as const }); + return team; + }, + + async addTeamMember(teamId: string, userId: string, role: MembershipRole = "member") { + const { error } = await supabase + .from("team_memberships") + .insert({ team_id: teamId, user_id: userId, role }); + if (error) throw new Error(error.message || "Unable to add member"); + }, + + // Projects + async addProjectMember(projectId: string, userId: string, role: ProjectRole = "contributor") { + const { error } = await supabase + .from("project_members") + .insert({ project_id: projectId, user_id: userId, role }); + if (error) throw new Error(error.message || "Unable to add project member"); + }, + + async listProjectMembers(projectId: string) { + const { data, error } = await supabase + .from("project_members") + .select("user_id, role, user:user_id ( id, full_name, username, avatar_url )") + .eq("project_id", projectId); + if (error) return [] as any[]; + return (data || []) as any[]; + }, + + // Tasks + async createTask(projectId: string, title: string, description?: string | null, assigneeId?: string | null, dueDate?: string | null) { + const { data, error } = await supabase + .from("project_tasks") + .insert({ project_id: projectId, title, description: description || null, assignee_id: assigneeId || null, due_date: dueDate || null }) + .select() + .single(); + if (error) throw new Error(error.message || "Unable to create task"); + return data as any; + }, + + async updateTaskStatus(taskId: string, status: TaskStatus) { + const { error } = await supabase + .from("project_tasks") + .update({ status }) + .eq("id", taskId); + if (error) throw new Error(error.message || "Unable to update task"); + }, + + // Activity bus publish + async publishActivity(params: { + actor_id: string; + verb: string; + object_type: string; + object_id?: string | null; + target_user_ids?: string[] | null; + target_team_id?: string | null; + target_project_id?: string | null; + metadata?: any; + }) { + const resp = await fetch("/api/activity/publish", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(params), + }); + if (!resp.ok) throw new Error(await resp.text()); + return await resp.json(); + }, +};