Create endpoint for joining/leaving sprints

cgen-cf52491cc5154f3186687679e8795212
This commit is contained in:
Builder.io 2025-11-15 17:02:40 +00:00
parent 01f57239a8
commit be5037df55

View file

@ -0,0 +1,149 @@
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { getAdminClient } from "../../_supabase";
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 {
// POST: Join a sprint
if (req.method === "POST") {
const { sprintId } = req.body;
if (!sprintId) {
return res.status(400).json({ error: "Sprint ID required" });
}
// Verify sprint exists and user has access to project
const { data: sprint, error: sprintError } = await admin
.from("gameforge_sprints")
.select("project_id")
.eq("id", sprintId)
.single();
if (sprintError || !sprint) {
return res.status(404).json({ error: "Sprint not found" });
}
// Check if user is part of the project
const { data: projectAccess } = await admin
.from("gameforge_projects")
.select("id")
.eq("id", sprint.project_id)
.or(`lead_id.eq.${user.id},id.in.(select project_id from gameforge_team_members where user_id='${user.id}')`)
.single();
if (!projectAccess) {
return res.status(403).json({
error: "You do not have access to this sprint's project",
});
}
// Check if already a member
const { data: existing } = await admin
.from("gameforge_sprint_members")
.select("id")
.eq("sprint_id", sprintId)
.eq("user_id", user.id)
.single();
if (existing) {
return res.status(400).json({ error: "Already a sprint member" });
}
// Add user to sprint
const { data: member, error: joinError } = await admin
.from("gameforge_sprint_members")
.insert([
{
sprint_id: sprintId,
user_id: user.id,
role: "contributor",
},
])
.select()
.single();
if (joinError) {
return res.status(500).json({ error: joinError.message });
}
return res.status(201).json({
message: "Successfully joined sprint",
member,
});
}
// DELETE: Leave a sprint
if (req.method === "DELETE") {
const { sprintId } = req.query;
if (!sprintId) {
return res.status(400).json({ error: "Sprint ID required" });
}
// Verify user is a member
const { data: member, error: memberError } = await admin
.from("gameforge_sprint_members")
.select("role")
.eq("sprint_id", sprintId)
.eq("user_id", user.id)
.single();
if (memberError || !member) {
return res
.status(404)
.json({ error: "Not a member of this sprint" });
}
// Don't allow lead to leave if they're the only lead
if (member.role === "lead") {
const { data: otherLeads } = await admin
.from("gameforge_sprint_members")
.select("id")
.eq("sprint_id", sprintId)
.eq("role", "lead")
.neq("user_id", user.id);
if (!otherLeads || otherLeads.length === 0) {
return res.status(400).json({
error: "Cannot leave sprint: You are the only lead",
});
}
}
const { error: deleteError } = await admin
.from("gameforge_sprint_members")
.delete()
.eq("sprint_id", sprintId)
.eq("user_id", user.id);
if (deleteError) {
return res.status(500).json({ error: deleteError.message });
}
return res.status(200).json({
message: "Successfully left sprint",
});
}
return res.status(405).json({ error: "Method not allowed" });
} catch (error: any) {
console.error("[GameForge Sprint Join]", error);
return res.status(500).json({ error: error?.message || "Server error" });
}
}