ClauseGuard / web /app /api /admin /route.ts
gaurv007's picture
v3.0: Fix admin API route — remove hardcoded email, check DB profiles.role only
f9d1091 verified
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 });
}
}