diff --git a/api/gameforge/metrics.ts b/api/gameforge/metrics.ts new file mode 100644 index 00000000..d9672f63 --- /dev/null +++ b/api/gameforge/metrics.ts @@ -0,0 +1,152 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient( + process.env.VITE_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +export default async function handler(req: any, res: any) { + const { method, query, body, headers } = req; + const userId = headers["x-user-id"]; + + try { + if (method === "GET") { + const { project_id, metric_type, limit = 12, offset = 0 } = query; + + if (!project_id) { + return res + .status(400) + .json({ error: "project_id query parameter required" }); + } + + let dbQuery = supabase + .from("gameforge_metrics") + .select("*", { count: "exact" }) + .eq("project_id", project_id); + + if (metric_type) dbQuery = dbQuery.eq("metric_type", metric_type); + + const { data, error, count } = await dbQuery + .order("metric_date", { ascending: false }) + .range(Number(offset), Number(offset) + Number(limit) - 1); + + if (error) throw error; + + // Calculate aggregates for the project + const { data: allMetrics } = await supabase + .from("gameforge_metrics") + .select("*") + .eq("project_id", project_id); + + const aggregates = allMetrics + ? { + avg_velocity: + allMetrics.length > 0 + ? Math.round( + allMetrics.reduce((sum, m) => sum + (m.velocity || 0), 0) / + allMetrics.length, + ) + : 0, + total_bugs_found: allMetrics.reduce( + (sum, m) => sum + (m.bugs_found || 0), + 0, + ), + total_bugs_fixed: allMetrics.reduce( + (sum, m) => sum + (m.bugs_fixed || 0), + 0, + ), + on_schedule_percentage: + allMetrics.length > 0 + ? Math.round( + (allMetrics.filter((m) => m.on_schedule).length / + allMetrics.length) * + 100, + ) + : 0, + avg_days_from_plan: + allMetrics.length > 0 + ? Math.round( + allMetrics.reduce( + (sum, m) => sum + (m.days_from_planned_to_release || 0), + 0, + ) / allMetrics.length, + ) + : 0, + } + : {}; + + return res.json({ + data, + aggregates, + total: count, + limit: Number(limit), + offset: Number(offset), + }); + } else if (method === "POST") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { + project_id, + metric_type, + velocity, + hours_logged, + team_size_avg, + bugs_found, + bugs_fixed, + build_count, + days_from_planned_to_release, + on_schedule, + budget_allocated, + budget_spent, + } = body; + + if (!project_id || !metric_type) { + return res.status(400).json({ + error: "Missing required fields: project_id, metric_type", + }); + } + + // Verify user is project lead + const { data: project } = await supabase + .from("gameforge_projects") + .select("lead_id") + .eq("id", project_id) + .single(); + + if (project?.lead_id !== userId) { + return res + .status(403) + .json({ error: "Only project lead can add metrics" }); + } + + const { data, error } = await supabase + .from("gameforge_metrics") + .insert([ + { + project_id, + metric_type, + metric_date: new Date().toISOString(), + velocity, + hours_logged, + team_size_avg, + bugs_found, + bugs_fixed, + build_count, + days_from_planned_to_release, + on_schedule, + budget_allocated, + budget_spent, + }, + ]) + .select(); + + if (error) throw error; + return res.status(201).json(data[0]); + } else { + return res.status(405).json({ error: "Method not allowed" }); + } + } catch (err: any) { + console.error("[GameForge Metrics]", err); + res.status(500).json({ error: err.message }); + } +}