Spaces:
Sleeping
Sleeping
| import { NextRequest, NextResponse } from "next/server"; | |
| import { createClient } from "@/lib/supabase/server"; | |
| // No hardcoded emails — admin access is determined by profiles.role in the database | |
| async function checkAdmin() { | |
| const supabase = await createClient(); | |
| const { data: { user } } = await supabase.auth.getUser(); | |
| if (!user) return { supabase: null, user: null, error: true }; | |
| // Check role from database | |
| const { data: profile } = await supabase | |
| .from("profiles") | |
| .select("role") | |
| .eq("id", user.id) | |
| .single(); | |
| if (profile?.role !== "admin") { | |
| return { supabase: null, user: null, error: true }; | |
| } | |
| return { supabase, user, error: false }; | |
| } | |
| // GET — admin stats + data | |
| export async function GET(req: NextRequest) { | |
| const { supabase, user, error } = await checkAdmin(); | |
| if (error || !supabase || !user) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| const url = new URL(req.url); | |
| const action = url.searchParams.get("action"); | |
| switch (action) { | |
| case "stats": { | |
| const { count: totalUsers } = await supabase.from("profiles").select("id", { count: "exact", head: true }); | |
| const { count: proUsers } = await supabase.from("profiles").select("id", { count: "exact", head: true }).eq("plan", "pro"); | |
| const { count: teamUsers } = await supabase.from("profiles").select("id", { count: "exact", head: true }).eq("plan", "team"); | |
| const { count: totalScans } = await supabase.from("analyses").select("id", { count: "exact", head: true }); | |
| const { count: totalTeams } = await supabase.from("teams").select("id", { count: "exact", head: true }); | |
| const { count: totalApiKeys } = await supabase.from("api_keys").select("id", { count: "exact", head: true }).eq("is_active", true); | |
| const { count: bannedUsers } = await supabase.from("profiles").select("id", { count: "exact", head: true }).eq("is_banned", true); | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| const { count: scansToday } = await supabase.from("analyses").select("id", { count: "exact", head: true }).gte("created_at", today.toISOString()); | |
| return NextResponse.json({ | |
| total_users: totalUsers || 0, | |
| pro_users: proUsers || 0, | |
| team_users: teamUsers || 0, | |
| free_users: (totalUsers || 0) - (proUsers || 0) - (teamUsers || 0), | |
| total_scans: totalScans || 0, | |
| scans_today: scansToday || 0, | |
| total_teams: totalTeams || 0, | |
| total_api_keys: totalApiKeys || 0, | |
| banned_users: bannedUsers || 0, | |
| }); | |
| } | |
| case "users": { | |
| const limit = parseInt(url.searchParams.get("limit") || "50"); | |
| const offset = parseInt(url.searchParams.get("offset") || "0"); | |
| const search = url.searchParams.get("search") || ""; | |
| let query = supabase.from("profiles") | |
| .select("id, email, full_name, plan, role, is_banned, analyses_this_month, team_id, razorpay_subscription_id, created_at", { count: "exact" }) | |
| .order("created_at", { ascending: false }) | |
| .range(offset, offset + limit - 1); | |
| if (search) { | |
| query = query.or(`email.ilike.%${search}%,full_name.ilike.%${search}%`); | |
| } | |
| const { data: users, count } = await query; | |
| return NextResponse.json({ users: users || [], total: count || 0 }); | |
| } | |
| case "scans": { | |
| const limit = parseInt(url.searchParams.get("limit") || "50"); | |
| const offset = parseInt(url.searchParams.get("offset") || "0"); | |
| const { data: scans, count } = await supabase.from("analyses") | |
| .select("id, user_id, source_url, risk_score, grade, flagged_count, total_clauses, created_at", { count: "exact" }) | |
| .order("created_at", { ascending: false }) | |
| .range(offset, offset + limit - 1); | |
| return NextResponse.json({ scans: scans || [], total: count || 0 }); | |
| } | |
| case "teams": { | |
| const { data: teams } = await supabase.from("teams").select("*").order("created_at", { ascending: false }); | |
| return NextResponse.json({ teams: teams || [] }); | |
| } | |
| case "api-keys": { | |
| const { data: keys } = await supabase.from("api_keys") | |
| .select("id, user_id, name, key_prefix, calls_this_month, calls_limit, is_active, created_at") | |
| .order("created_at", { ascending: false }); | |
| return NextResponse.json({ keys: keys || [] }); | |
| } | |
| case "logs": { | |
| const { data: logs } = await supabase.from("admin_logs") | |
| .select("*").order("created_at", { ascending: false }).limit(100); | |
| return NextResponse.json({ logs: logs || [] }); | |
| } | |
| default: | |
| return NextResponse.json({ error: "Invalid action" }, { status: 400 }); | |
| } | |
| } | |
| // POST — admin actions | |
| export async function POST(req: NextRequest) { | |
| const { supabase, user, error } = await checkAdmin(); | |
| if (error || !supabase || !user) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| const body = await req.json(); | |
| switch (body.action) { | |
| case "update_plan": { | |
| const { userId, plan } = body; | |
| await supabase.from("profiles").update({ plan, updated_at: new Date().toISOString() }).eq("id", userId); | |
| await supabase.from("admin_logs").insert({ | |
| admin_id: user.id, action: "update_plan", target_type: "user", target_id: userId, | |
| details: { plan }, | |
| }); | |
| return NextResponse.json({ success: true }); | |
| } | |
| case "ban_user": { | |
| const { userId, banned } = body; | |
| await supabase.from("profiles").update({ is_banned: banned, updated_at: new Date().toISOString() }).eq("id", userId); | |
| await supabase.from("admin_logs").insert({ | |
| admin_id: user.id, action: banned ? "ban_user" : "unban_user", target_type: "user", target_id: userId, | |
| }); | |
| return NextResponse.json({ success: true }); | |
| } | |
| case "delete_scan": { | |
| const { scanId } = body; | |
| await supabase.from("analyses").delete().eq("id", scanId); | |
| await supabase.from("admin_logs").insert({ | |
| admin_id: user.id, action: "delete_scan", target_type: "scan", target_id: scanId, | |
| }); | |
| return NextResponse.json({ success: true }); | |
| } | |
| case "revoke_api_key": { | |
| const { keyId } = body; | |
| await supabase.from("api_keys").update({ is_active: false }).eq("id", keyId); | |
| await supabase.from("admin_logs").insert({ | |
| admin_id: user.id, action: "revoke_api_key", target_type: "api_key", target_id: keyId, | |
| }); | |
| return NextResponse.json({ success: true }); | |
| } | |
| case "delete_team": { | |
| const { teamId } = body; | |
| await supabase.from("profiles").update({ team_id: null }).eq("team_id", teamId); | |
| await supabase.from("teams").delete().eq("id", teamId); | |
| await supabase.from("admin_logs").insert({ | |
| admin_id: user.id, action: "delete_team", target_type: "team", target_id: teamId, | |
| }); | |
| return NextResponse.json({ success: true }); | |
| } | |
| default: | |
| return NextResponse.json({ error: "Invalid action" }, { status: 400 }); | |
| } | |
| } | |