import { NextRequest, NextResponse } from 'next/server'; import { prisma, projectService } from '@/lib/prisma'; import { analyticsCache } from '@/lib/redis'; import { requireSessionAuth, checkRateLimit, getRateLimitHeaders } from '@/lib/auth'; export async function GET(request: NextRequest) { try { // Rate limiting - more generous for admin dashboard const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; if (!checkRateLimit(ip, 20, 60000)) { // 20 requests per minute return new NextResponse( JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', ...getRateLimitHeaders(ip, 5, 60000) } } ); } // Check admin authentication - for admin dashboard requests, we trust the session // The middleware has already verified the admin session for /manage routes const isAdminRequest = request.headers.get('x-admin-request') === 'true'; if (!isAdminRequest) { const authError = requireSessionAuth(request); if (authError) { return authError; } } // Check cache first (but allow bypass with cache-bust parameter) const url = new URL(request.url); const bypassCache = url.searchParams.get('nocache') === 'true'; if (!bypassCache) { const cachedStats = await analyticsCache.getOverallStats(); if (cachedStats) { return NextResponse.json(cachedStats); } } // Get analytics data const projectsResult = await projectService.getAllProjects(); const projects = projectsResult.projects || projectsResult; const performanceStats = await projectService.getPerformanceStats(); // Get real page view data from database const allPageViews = await prisma.pageView.findMany({ where: { timestamp: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // Last 30 days } } }); // Calculate bounce rate (sessions with only 1 pageview) const pageViewsByIP = allPageViews.reduce((acc, pv) => { const ip = pv.ip || 'unknown'; if (!acc[ip]) acc[ip] = []; acc[ip].push(pv); return acc; }, {} as Record); const totalSessions = Object.keys(pageViewsByIP).length; const bouncedSessions = Object.values(pageViewsByIP).filter(session => session.length === 1).length; const bounceRate = totalSessions > 0 ? Math.round((bouncedSessions / totalSessions) * 100) : 0; // Calculate average session duration (simplified - time between first and last pageview per IP) const sessionDurations = Object.values(pageViewsByIP) .map(session => { if (session.length < 2) return 0; const sorted = session.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); return sorted[sorted.length - 1].timestamp.getTime() - sorted[0].timestamp.getTime(); }) .filter(d => d > 0); const avgSessionDuration = sessionDurations.length > 0 ? Math.round(sessionDurations.reduce((a, b) => a + b, 0) / sessionDurations.length / 1000) // in seconds : 0; // Get total unique users (unique IPs) const totalUsers = new Set(allPageViews.map(pv => pv.ip).filter(Boolean)).size; // Calculate real views from PageView table const viewsByProject = allPageViews.reduce((acc, pv) => { if (pv.projectId) { acc[pv.projectId] = (acc[pv.projectId] || 0) + 1; } return acc; }, {} as Record); // Calculate analytics metrics const analytics = { overview: { totalProjects: projects.length, publishedProjects: projects.filter(p => p.published).length, featuredProjects: projects.filter(p => p.featured).length, totalViews: allPageViews.length, // Real views from PageView table totalLikes: 0, // Not implemented - no like buttons totalShares: 0, // Not implemented - no share buttons avgLighthouse: (() => { // Only calculate if we have real performance data (not defaults) const projectsWithPerf = projects.filter(p => { const perf = (p.performance as Record) || {}; const lighthouse = perf.lighthouse as number || 0; return lighthouse > 0; // Only count projects with actual performance data }); return projectsWithPerf.length > 0 ? Math.round(projectsWithPerf.reduce((sum, p) => sum + ((p.performance as Record)?.lighthouse as number || 0), 0) / projectsWithPerf.length) : 0; })() }, projects: projects.map(project => ({ id: project.id, title: project.title, category: project.category, difficulty: project.difficulty, views: viewsByProject[project.id] || 0, // Only real views from PageView table likes: 0, // Not implemented shares: 0, // Not implemented lighthouse: (() => { const perf = (project.performance as Record) || {}; const score = perf.lighthouse as number || 0; return score > 0 ? score : 0; // Only return if we have real data })(), published: project.published, featured: project.featured, createdAt: project.createdAt, updatedAt: project.updatedAt })), categories: performanceStats.byCategory, difficulties: performanceStats.byDifficulty, performance: { avgLighthouse: (() => { const projectsWithPerf = projects.filter(p => { const perf = (p.performance as Record) || {}; return (perf.lighthouse as number || 0) > 0; }); return projectsWithPerf.length > 0 ? Math.round(projectsWithPerf.reduce((sum, p) => sum + ((p.performance as Record)?.lighthouse as number || 0), 0) / projectsWithPerf.length) : 0; })(), totalViews: allPageViews.length, // Real total views totalLikes: 0, totalShares: 0 }, metrics: { bounceRate, avgSessionDuration, pagesPerSession: totalSessions > 0 ? (allPageViews.length / totalSessions).toFixed(1) : '0', newUsers: totalUsers, totalUsers } }; // Cache the results await analyticsCache.setOverallStats(analytics); return NextResponse.json(analytics); } catch (error) { console.error('Analytics dashboard error:', error); return NextResponse.json( { error: 'Failed to fetch analytics data' }, { status: 500 } ); } }