import { serve } from "https://deno.land/std@0.203.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; // CORS: allow browser-based web clients (adjust origin in production) // Permit headers the supabase-js client sends (e.g. `apikey`, `x-client-info`) so // browser preflight requests succeed. Keep origin wide for dev; in production // prefer echoing the request origin and enabling credentials only when needed. const CORS_HEADERS: Record = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, apikey, x-client-info, x-requested-with, Origin, Accept", "Access-Control-Max-Age": "3600", }; const jsonResponse = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...CORS_HEADERS }, }); serve(async (req) => { // Handle CORS preflight quickly so browsers can call this function from localhost/dev if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers: CORS_HEADERS }); } const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? ""; const anonKey = Deno.env.get("SUPABASE_ANON_KEY") ?? ""; const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""; if (!supabaseUrl || !anonKey || !serviceKey) { return jsonResponse({ error: "Missing env configuration" }, 500); } // Authenticate caller (must be admin) const authHeader = req.headers.get("Authorization") ?? ""; const token = authHeader.replace("Bearer ", "").trim(); if (!token) return jsonResponse({ error: "Missing access token" }, 401); const authClient = createClient(supabaseUrl, anonKey, { global: { headers: { Authorization: `Bearer ${token}` } }, }); const { data: authData, error: authError } = await authClient.auth.getUser(); if (authError || !authData?.user) return jsonResponse({ error: "Unauthorized" }, 401); // Use service role client for privileged DB ops const adminClient = createClient(supabaseUrl, serviceKey); // Ensure caller is admin in profiles table const { data: profile, error: profileError } = await adminClient .from("profiles") .select("role") .eq("id", authData.user.id) .maybeSingle(); const role = (profile?.role ?? "").toString().toLowerCase(); if (profileError || role !== "admin") return jsonResponse({ error: "Forbidden" }, 403); // Route request const url = new URL(req.url); const id = url.pathname.split("/").pop(); try { if (req.method === "GET") { if (id && id !== "teams") { const { data, error } = await adminClient .from("teams") .select('*, team_members(*)') .eq('id', id) .maybeSingle(); if (error) return jsonResponse({ error: error.message }, 400); return jsonResponse({ team: data }); } const { data, error } = await adminClient .from("teams") .select('*, team_members(*)') .order('name'); if (error) return jsonResponse({ error: error.message }, 400); return jsonResponse({ teams: data }); } if (req.method === "POST") { // Support both: standard POST-create and POST with an 'action' parameter const body = await req.json(); const action = typeof body.action === 'string' ? body.action.toLowerCase() : 'create'; // Create (default POST) if (action === 'create') { const name = typeof body.name === 'string' ? body.name : undefined; const leaderId = typeof body.leader_id === 'string' ? body.leader_id : undefined; const officeIds = Array.isArray(body.office_ids) ? (body.office_ids as string[]) : []; const members = Array.isArray(body.members) ? (body.members as string[]) : []; if (!name || !leaderId || officeIds.length === 0) { return jsonResponse({ error: 'Missing required fields' }, 400); } const { data: insertedTeam, error: insertError } = await adminClient .from('teams') .insert({ name, leader_id: leaderId, office_ids: officeIds }) .select() .single(); if (insertError) return jsonResponse({ error: insertError.message }, 400); if (members.length > 0) { const rows = members.map((u) => ({ team_id: (insertedTeam as any).id, user_id: u })); const { error } = await adminClient.from('team_members').insert(rows); if (error) return jsonResponse({ error: error.message }, 400); } return jsonResponse({ team: insertedTeam }, 201); } // Update (POST with action='update') if (action === 'update') { const teamId = typeof body.id === 'string' ? body.id : undefined; if (!teamId) return jsonResponse({ error: 'Missing team id' }, 400); const name = typeof body.name === 'string' ? body.name : undefined; const leaderId = typeof body.leader_id === 'string' ? body.leader_id : undefined; const officeIds = Array.isArray(body.office_ids) ? (body.office_ids as string[]) : []; const members = Array.isArray(body.members) ? (body.members as string[]) : []; const { error: upErr } = await adminClient .from('teams') .update({ name, leader_id: leaderId, office_ids: officeIds }) .eq('id', teamId); if (upErr) return jsonResponse({ error: upErr.message }, 400); await adminClient.from('team_members').delete().eq('team_id', teamId); if (members.length > 0) { const rows = members.map((u) => ({ team_id: teamId, user_id: u })); const { error } = await adminClient.from('team_members').insert(rows); if (error) return jsonResponse({ error: error.message }, 400); } const { data: updated, error: fetchErr } = await adminClient.from('teams').select().eq('id', teamId).maybeSingle(); if (fetchErr) return jsonResponse({ error: fetchErr.message }, 400); return jsonResponse({ team: updated }); } // Delete (POST with action='delete') if (action === 'delete') { const teamId = typeof body.id === 'string' ? body.id : undefined; if (!teamId) return jsonResponse({ error: 'Missing team id' }, 400); await adminClient.from('team_members').delete().eq('team_id', teamId); const { error } = await adminClient.from('teams').delete().eq('id', teamId); if (error) return jsonResponse({ error: error.message }, 400); return jsonResponse({ ok: true }); } return jsonResponse({ error: 'Unknown POST action' }, 400); } if (req.method === "PUT") { if (!id || id === "teams") return jsonResponse({ error: "Missing team id" }, 400); const body = await req.json(); const name = body.name as string | undefined; const leaderId = body.leader_id as string | undefined; const officeIds = (body.office_ids as string[] | undefined) ?? []; const members = (body.members as string[] | undefined) ?? []; const { error: upErr } = await adminClient.from('teams').update({ name, leader_id: leaderId, office_ids: officeIds }).eq('id', id); if (upErr) return jsonResponse({ error: upErr.message }, 400); await adminClient.from('team_members').delete().eq('team_id', id); if (members.length > 0) { const rows = members.map((u) => ({ team_id: id, user_id: u })); const { error } = await adminClient.from('team_members').insert(rows); if (error) return jsonResponse({ error: error.message }, 400); } const { data: updated, error: fetchErr } = await adminClient.from('teams').select().eq('id', id).maybeSingle(); if (fetchErr) return jsonResponse({ error: fetchErr.message }, 400); return jsonResponse({ team: updated }); } if (req.method === "DELETE") { if (!id || id === "teams") return jsonResponse({ error: "Missing team id" }, 400); await adminClient.from('team_members').delete().eq('team_id', id); const { error } = await adminClient.from('teams').delete().eq('id', id); if (error) return jsonResponse({ error: error.message }, 400); return jsonResponse({ ok: true }); } return jsonResponse({ error: "Method not allowed" }, 405); } catch (err) { return jsonResponse({ error: (err as Error).message }, 500); } });