From 2ccafa0f34532ecf14523d6f2c126679929556b2 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 1 Nov 2025 16:11:11 +0530 Subject: [PATCH] feat(admin): Add dynamic recent activities and system health fetching - Refactor admin dashboard to dynamically fetch recent activities via API - Create new API route `/api/admin/recent-activities` to retrieve system events - Add state management for recent activities and system health data - Implement authorization checks in recent activities API route - Fetch activities from multiple sources: user signups, blog posts, events, and hackathons - Sort and limit activities to display most recent system events - Remove hardcoded activities and replace with dynamic data fetching - Improve type safety with explicit type definitions for activities and system health --- app/admin/page.tsx | 99 ++++++---------- app/api/admin/recent-activities/route.ts | 139 +++++++++++++++++++++++ app/api/admin/system-health/route.ts | 79 +++++++++++++ 3 files changed, 254 insertions(+), 63 deletions(-) create mode 100644 app/api/admin/recent-activities/route.ts create mode 100644 app/api/admin/system-health/route.ts diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 0e41db6b..bddc1cef 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -87,70 +87,20 @@ const dashboardStats = [ }, ] -const recentActivities = [ - { - id: 1, - type: "user_signup", - message: "New user registered: akshay@gmail.com", - timestamp: "2 minutes ago", - status: "success", - }, - { - id: 2, - type: "test_created", - message: "New test 'JavaScript Fundamentals' created", - timestamp: "5 minutes ago", - status: "success", - }, - { - id: 3, - type: "blog_published", - message: "Blog post 'React 18 Features' published by Akshay", - timestamp: "15 minutes ago", - status: "success", - }, - { - id: 4, - type: "event_created", - message: "New hackathon 'Summer Challenge 2025' created", - timestamp: "1 hour ago", - status: "info", - }, - { - id: 5, - type: "security_alert", - message: "Multiple failed login attempts detected", - timestamp: "2 hours ago", - status: "warning", - } -] +type Activity = { + id: number + type: string + message: string + timestamp: string + status: string +} -const systemHealth = [ - { - service: "API Server", - status: "healthy", - uptime: "99.9%", - responseTime: "45ms", - }, - { - service: "Database", - status: "healthy", - uptime: "99.8%", - responseTime: "12ms", - }, - { - service: "CDN", - status: "healthy", - uptime: "100%", - responseTime: "23ms", - }, - { - service: "Email Service", - status: "warning", - uptime: "98.5%", - responseTime: "156ms", - }, -] +type SystemHealthItem = { + service: string + status: string + uptime: string + responseTime: string +} export default function AdminDashboard() { const [currentTime, setCurrentTime] = useState(new Date()) @@ -162,6 +112,8 @@ export default function AdminDashboard() { const [pageViewsChange, setPageViewsChange] = useState("") const [pageViewsTrend, setPageViewsTrend] = useState<"up" | "down">("up") const [topContent, setTopContent] = useState([]) + const [recentActivities, setRecentActivities] = useState([]) + const [systemHealth, setSystemHealth] = useState([]) const supabaseRef = useRef | null>(null) const likesChannelRef = useRef(null) @@ -249,6 +201,27 @@ export default function AdminDashboard() { } } fetchTopContentWithLikes() + + // Fetch recent activities + fetch("/api/admin/recent-activities") + .then(res => res.json()) + .then(data => { + if (data.activities) { + setRecentActivities(data.activities) + } + }) + .catch(err => console.error("Failed to fetch activities:", err)) + + // Fetch system health + fetch("/api/admin/system-health") + .then(res => res.json()) + .then(data => { + if (data.health) { + setSystemHealth(data.health) + } + }) + .catch(err => console.error("Failed to fetch system health:", err)) + // Setup realtime subscription const supabase = createClient() supabaseRef.current = supabase diff --git a/app/api/admin/recent-activities/route.ts b/app/api/admin/recent-activities/route.ts new file mode 100644 index 00000000..2401bf8e --- /dev/null +++ b/app/api/admin/recent-activities/route.ts @@ -0,0 +1,139 @@ +import { NextResponse } from "next/server" +import { createClient } from "@/lib/supabase/server" + +export async function GET() { + try { + const supabase = await createClient() + + // Check if user is admin + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { data: profile } = await supabase + .from("profiles") + .select("is_admin") + .eq("id", user.id) + .single() + + if (!profile?.is_admin) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }) + } + + const activities: Array<{ + type: string + message: string + timestamp: string + status: string + created_at: string + }> = [] + + // Fetch recent user signups (last 7 days to ensure we have data) + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString() + + const { data: recentUsers } = await supabase + .from("profiles") + .select("email, created_at") + .gte("created_at", sevenDaysAgo) + .order("created_at", { ascending: false }) + .limit(3) + + if (recentUsers) { + recentUsers.forEach(user => { + activities.push({ + type: "user_signup", + message: `New user registered: ${user.email}`, + timestamp: getTimeAgo(user.created_at), + status: "success", + created_at: user.created_at + }) + }) + } + + // Fetch recent blog posts + const { data: recentBlogs } = await supabase + .from("blogs") + .select("title, author, date") + .order("date", { ascending: false }) + .limit(3) + + if (recentBlogs) { + recentBlogs.forEach(blog => { + activities.push({ + type: "blog_published", + message: `Blog post "${blog.title}" published by ${blog.author}`, + timestamp: getTimeAgo(blog.date), + status: "success", + created_at: blog.date + }) + }) + } + + // Fetch recent events + const { data: recentEvents } = await supabase + .from("events") + .select("title, created_at") + .order("created_at", { ascending: false }) + .limit(2) + + if (recentEvents) { + recentEvents.forEach(event => { + activities.push({ + type: "event_created", + message: `New event "${event.title}" created`, + timestamp: getTimeAgo(event.created_at), + status: "info", + created_at: event.created_at + }) + }) + } + + // Fetch recent hackathons + const { data: recentHackathons } = await supabase + .from("hackathons") + .select("title, created_at") + .order("created_at", { ascending: false }) + .limit(2) + + if (recentHackathons) { + recentHackathons.forEach(hackathon => { + activities.push({ + type: "event_created", + message: `New hackathon "${hackathon.title}" created`, + timestamp: getTimeAgo(hackathon.created_at), + status: "info", + created_at: hackathon.created_at + }) + }) + } + + // Sort all activities by timestamp and limit to 5 + activities.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + const topActivities = activities.slice(0, 5).map((activity, index) => ({ + id: index + 1, + type: activity.type, + message: activity.message, + timestamp: activity.timestamp, + status: activity.status + })) + + return NextResponse.json({ activities: topActivities }) + } catch (error) { + console.error("Recent activities error:", error) + return NextResponse.json({ error: "Internal server error" }, { status: 500 }) + } +} + +function getTimeAgo(dateString: string): string { + const date = new Date(dateString) + const now = new Date() + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000) + + if (seconds < 60) return `${seconds} seconds ago` + if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago` + if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago` + if (seconds < 604800) return `${Math.floor(seconds / 86400)} days ago` + return `${Math.floor(seconds / 604800)} weeks ago` +} diff --git a/app/api/admin/system-health/route.ts b/app/api/admin/system-health/route.ts new file mode 100644 index 00000000..b9cd56cf --- /dev/null +++ b/app/api/admin/system-health/route.ts @@ -0,0 +1,79 @@ +import { NextResponse } from "next/server" +import { createClient } from "@/lib/supabase/server" + +export async function GET() { + try { + const supabase = await createClient() + + // Check if user is admin + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { data: profile } = await supabase + .from("profiles") + .select("is_admin") + .eq("id", user.id) + .single() + + if (!profile?.is_admin) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }) + } + + const healthChecks = [] + + // Check Database + const dbStart = Date.now() + const { error: dbError } = await supabase + .from("profiles") + .select("id") + .limit(1) + const dbTime = Date.now() - dbStart + + healthChecks.push({ + service: "Database", + status: dbError ? "error" : dbTime < 100 ? "healthy" : "warning", + uptime: dbError ? "Down" : "Operational", + responseTime: `${dbTime}ms` + }) + + // Check API Server (this endpoint itself) + const apiTime = Date.now() - dbStart + healthChecks.push({ + service: "API Server", + status: "healthy", + uptime: "Operational", + responseTime: `${apiTime}ms` + }) + + // Check Supabase Storage + const storageStart = Date.now() + const { error: storageError } = await supabase.storage + .from("blog-images") + .list("public", { limit: 1 }) + const storageTime = Date.now() - storageStart + + healthChecks.push({ + service: "Storage (CDN)", + status: storageError ? "error" : storageTime < 300 ? "healthy" : "warning", + uptime: storageError ? "Down" : "Operational", + responseTime: `${storageTime}ms` + }) + + // Check Email Service (Resend) + const resendConfigured = !!process.env.RESEND_API_KEY + healthChecks.push({ + service: "Email Service", + status: resendConfigured ? "healthy" : "warning", + uptime: resendConfigured ? "Configured" : "Not Configured", + responseTime: resendConfigured ? "Ready" : "N/A" + }) + + return NextResponse.json({ health: healthChecks }) + } catch (error) { + console.error("System health error:", error) + return NextResponse.json({ error: "Internal server error" }, { status: 500 }) + } +}