211 lines
5.6 KiB
TypeScript
211 lines
5.6 KiB
TypeScript
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
|
import { getAdminClient } from "../_supabase.js";
|
|
|
|
const admin = getAdminClient();
|
|
|
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader) {
|
|
return res.status(401).json({ error: "Unauthorized" });
|
|
}
|
|
|
|
const token = authHeader.replace("Bearer ", "");
|
|
const {
|
|
data: { user },
|
|
error: authError,
|
|
} = await admin.auth.getUser(token);
|
|
|
|
if (authError || !user) {
|
|
return res.status(401).json({ error: "Invalid token" });
|
|
}
|
|
|
|
try {
|
|
// GET: List tasks for sprint or project
|
|
if (req.method === "GET") {
|
|
const { sprintId, projectId, status, assignedTo } = req.query;
|
|
|
|
let query = admin.from("gameforge_tasks").select(
|
|
`
|
|
id,
|
|
sprint_id,
|
|
project_id,
|
|
title,
|
|
description,
|
|
status,
|
|
priority,
|
|
estimated_hours,
|
|
actual_hours,
|
|
assigned_to,
|
|
created_by,
|
|
due_date,
|
|
completed_at,
|
|
created_at,
|
|
updated_at,
|
|
user_profiles:assigned_to(id, full_name, avatar_url),
|
|
creator:created_by_id(id, full_name)
|
|
`,
|
|
);
|
|
|
|
if (sprintId) {
|
|
query = query.eq("sprint_id", sprintId);
|
|
}
|
|
|
|
if (projectId) {
|
|
query = query.eq("project_id", projectId);
|
|
}
|
|
|
|
if (status) {
|
|
query = query.eq("status", status);
|
|
}
|
|
|
|
if (assignedTo) {
|
|
query = query.eq("assigned_to", assignedTo);
|
|
}
|
|
|
|
const { data: tasks, error } = await query.order("created_at", {
|
|
ascending: false,
|
|
});
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
return res.status(200).json(tasks || []);
|
|
}
|
|
|
|
// POST: Create a task
|
|
if (req.method === "POST") {
|
|
const {
|
|
sprintId,
|
|
projectId,
|
|
title,
|
|
description,
|
|
priority,
|
|
estimatedHours,
|
|
assignedTo,
|
|
dueDate,
|
|
} = req.body;
|
|
|
|
if (!projectId || !title) {
|
|
return res.status(400).json({ error: "Project ID and title required" });
|
|
}
|
|
|
|
// Verify user has access to project (lead or team member)
|
|
const { data: project, error: projectError } = await admin
|
|
.from("gameforge_projects")
|
|
.select("id")
|
|
.eq("id", projectId)
|
|
.or(
|
|
`lead_id.eq.${user.id},id.in.(select project_id from gameforge_team_members where user_id='${user.id}')`,
|
|
)
|
|
.single();
|
|
|
|
if (projectError || !project) {
|
|
return res.status(403).json({ error: "No access to project" });
|
|
}
|
|
|
|
// If assigning to someone, verify they're on the project
|
|
if (assignedTo && assignedTo !== user.id) {
|
|
const { data: assignee } = await admin
|
|
.from("gameforge_team_members")
|
|
.select("id")
|
|
.eq("user_id", assignedTo)
|
|
.contains("project_ids", [projectId])
|
|
.single();
|
|
|
|
if (!assignee) {
|
|
return res.status(400).json({
|
|
error: "Assignee is not on this project",
|
|
});
|
|
}
|
|
}
|
|
|
|
const { data: task, error: createError } = await admin
|
|
.from("gameforge_tasks")
|
|
.insert([
|
|
{
|
|
sprint_id: sprintId || null,
|
|
project_id: projectId,
|
|
title,
|
|
description,
|
|
priority: priority || "medium",
|
|
estimated_hours: estimatedHours,
|
|
assigned_to: assignedTo || null,
|
|
created_by: user.id,
|
|
due_date: dueDate || null,
|
|
status: "todo",
|
|
},
|
|
])
|
|
.select()
|
|
.single();
|
|
|
|
if (createError) {
|
|
return res.status(500).json({ error: createError.message });
|
|
}
|
|
|
|
return res.status(201).json(task);
|
|
}
|
|
|
|
// PUT: Update a task
|
|
if (req.method === "PUT") {
|
|
const { taskId } = req.query;
|
|
const { status, priority, estimatedHours, actualHours, assignedTo } =
|
|
req.body;
|
|
|
|
if (!taskId) {
|
|
return res.status(400).json({ error: "Task ID required" });
|
|
}
|
|
|
|
// Verify user can edit (assigned or project lead)
|
|
const { data: task, error: taskError } = await admin
|
|
.from("gameforge_tasks")
|
|
.select("project_id, assigned_to, created_by")
|
|
.eq("id", taskId)
|
|
.single();
|
|
|
|
if (taskError || !task) {
|
|
return res.status(404).json({ error: "Task not found" });
|
|
}
|
|
|
|
const { data: project } = await admin
|
|
.from("gameforge_projects")
|
|
.select("id")
|
|
.eq("id", task.project_id)
|
|
.eq("lead_id", user.id)
|
|
.single();
|
|
|
|
if (
|
|
!project &&
|
|
task.assigned_to !== user.id &&
|
|
task.created_by !== user.id
|
|
) {
|
|
return res.status(403).json({ error: "No permission to edit task" });
|
|
}
|
|
|
|
const { data: updated, error: updateError } = await admin
|
|
.from("gameforge_tasks")
|
|
.update({
|
|
status,
|
|
priority,
|
|
estimated_hours: estimatedHours,
|
|
actual_hours: actualHours,
|
|
assigned_to: assignedTo,
|
|
completed_at: status === "done" ? new Date().toISOString() : null,
|
|
})
|
|
.eq("id", taskId)
|
|
.select()
|
|
.single();
|
|
|
|
if (updateError) {
|
|
return res.status(500).json({ error: updateError.message });
|
|
}
|
|
|
|
return res.status(200).json(updated);
|
|
}
|
|
|
|
return res.status(405).json({ error: "Method not allowed" });
|
|
} catch (error: any) {
|
|
console.error("[GameForge Tasks]", error);
|
|
return res.status(500).json({ error: error?.message || "Server error" });
|
|
}
|
|
}
|