aethex-forge/api/admin/analytics.ts
Claude 88e364f4c5
Add admin moderation and analytics dashboards
Moderation Dashboard:
- API with admin-only access for content moderation
- View/filter reports by status (open, resolved, ignored)
- View/resolve contract disputes
- Manage flagged users (warn, ban, unban)
- Resolution notes for audit trail
- Stats for open reports, disputes, resolved today

Analytics Dashboard:
- Comprehensive platform metrics API
- User stats: total, new, active, creators
- Opportunity and application metrics
- Contract tracking and completion rates
- Revenue tracking by period
- Daily signup trend visualization
- Top performing opportunities ranking
- Period selector (7, 30, 90, 365 days)

Both dashboards have proper admin authorization checks.
2026-01-26 22:39:47 +00:00

187 lines
6.3 KiB
TypeScript

import { supabase } from "../_supabase.js";
export default async (req: Request) => {
const token = req.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) {
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
}
const { data: userData } = await supabase.auth.getUser(token);
if (!userData.user) {
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
}
// Check if user is admin
const { data: profile } = await supabase
.from("profiles")
.select("role")
.eq("id", userData.user.id)
.single();
if (!profile || profile.role !== "admin") {
return new Response(JSON.stringify({ error: "Forbidden" }), { status: 403, headers: { "Content-Type": "application/json" } });
}
const url = new URL(req.url);
const period = url.searchParams.get("period") || "30"; // days
try {
if (req.method === "GET") {
const daysAgo = new Date();
daysAgo.setDate(daysAgo.getDate() - parseInt(period));
// Get total users and growth
const { count: totalUsers } = await supabase
.from("profiles")
.select("*", { count: "exact", head: true });
const { count: newUsersThisPeriod } = await supabase
.from("profiles")
.select("*", { count: "exact", head: true })
.gte("created_at", daysAgo.toISOString());
// Get active users (logged in within period)
const { count: activeUsers } = await supabase
.from("profiles")
.select("*", { count: "exact", head: true })
.gte("last_login_at", daysAgo.toISOString());
// Get opportunities stats
const { count: totalOpportunities } = await supabase
.from("aethex_opportunities")
.select("*", { count: "exact", head: true });
const { count: openOpportunities } = await supabase
.from("aethex_opportunities")
.select("*", { count: "exact", head: true })
.eq("status", "open");
const { count: newOpportunities } = await supabase
.from("aethex_opportunities")
.select("*", { count: "exact", head: true })
.gte("created_at", daysAgo.toISOString());
// Get applications stats
const { count: totalApplications } = await supabase
.from("aethex_applications")
.select("*", { count: "exact", head: true });
const { count: newApplications } = await supabase
.from("aethex_applications")
.select("*", { count: "exact", head: true })
.gte("created_at", daysAgo.toISOString());
// Get contracts stats
const { count: totalContracts } = await supabase
.from("nexus_contracts")
.select("*", { count: "exact", head: true });
const { count: activeContracts } = await supabase
.from("nexus_contracts")
.select("*", { count: "exact", head: true })
.eq("status", "active");
// Get community stats
const { count: totalPosts } = await supabase
.from("community_posts")
.select("*", { count: "exact", head: true });
const { count: newPosts } = await supabase
.from("community_posts")
.select("*", { count: "exact", head: true })
.gte("created_at", daysAgo.toISOString());
// Get creator stats
const { count: totalCreators } = await supabase
.from("aethex_creators")
.select("*", { count: "exact", head: true });
// Get daily signups for trend (last 30 days)
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const { data: signupTrend } = await supabase
.from("profiles")
.select("created_at")
.gte("created_at", thirtyDaysAgo.toISOString())
.order("created_at");
// Group signups by date
const signupsByDate: Record<string, number> = {};
signupTrend?.forEach((user) => {
const date = new Date(user.created_at).toISOString().split("T")[0];
signupsByDate[date] = (signupsByDate[date] || 0) + 1;
});
const dailySignups = Object.entries(signupsByDate).map(([date, count]) => ({
date,
count
}));
// Revenue data (if available)
const { data: revenueData } = await supabase
.from("nexus_payments")
.select("amount, created_at")
.eq("status", "completed")
.gte("created_at", daysAgo.toISOString());
const totalRevenue = revenueData?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0;
// Top performing opportunities
const { data: topOpportunities } = await supabase
.from("aethex_opportunities")
.select(`
id,
title,
aethex_applications(count)
`)
.eq("status", "open")
.order("created_at", { ascending: false })
.limit(5);
return new Response(JSON.stringify({
users: {
total: totalUsers || 0,
new: newUsersThisPeriod || 0,
active: activeUsers || 0,
creators: totalCreators || 0
},
opportunities: {
total: totalOpportunities || 0,
open: openOpportunities || 0,
new: newOpportunities || 0
},
applications: {
total: totalApplications || 0,
new: newApplications || 0
},
contracts: {
total: totalContracts || 0,
active: activeContracts || 0
},
community: {
posts: totalPosts || 0,
newPosts: newPosts || 0
},
revenue: {
total: totalRevenue,
period: `${period} days`
},
trends: {
dailySignups,
topOpportunities: topOpportunities?.map(o => ({
id: o.id,
title: o.title,
applications: o.aethex_applications?.[0]?.count || 0
})) || []
},
period: parseInt(period)
}), { headers: { "Content-Type": "application/json" } });
}
return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { "Content-Type": "application/json" } });
} catch (err: any) {
console.error("Analytics API error:", err);
return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { "Content-Type": "application/json" } });
}
};