Create endpoint for joining/leaving sprints
cgen-cf52491cc5154f3186687679e8795212
This commit is contained in:
parent
01f57239a8
commit
be5037df55
1 changed files with 149 additions and 0 deletions
149
api/gameforge/sprint-join.ts
Normal file
149
api/gameforge/sprint-join.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue