- Integrate real page view data from the database for accurate analytics. - Implement cache-busting for fresh data retrieval in analytics dashboard. - Calculate and display bounce rate, average session duration, and unique users. - Refactor performance metrics to ensure only real data is considered. - Improve user experience with toast notifications for success and error messages. - Update project editor with undo/redo functionality and enhanced content management.
142 lines
5.6 KiB
TypeScript
142 lines
5.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { requireSessionAuth } from '@/lib/auth';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
// Check admin authentication - for admin dashboard requests, we trust the session
|
|
const isAdminRequest = request.headers.get('x-admin-request') === 'true';
|
|
if (!isAdminRequest) {
|
|
const authError = requireSessionAuth(request);
|
|
if (authError) {
|
|
return authError;
|
|
}
|
|
}
|
|
|
|
// Get performance data from database
|
|
const pageViews = await prisma.pageView.findMany({
|
|
orderBy: { timestamp: 'desc' },
|
|
take: 1000 // Last 1000 page views
|
|
});
|
|
|
|
const userInteractions = await prisma.userInteraction.findMany({
|
|
orderBy: { timestamp: 'desc' },
|
|
take: 1000 // Last 1000 interactions
|
|
});
|
|
|
|
// Get all projects for performance data
|
|
const projects = await prisma.project.findMany();
|
|
|
|
// Calculate real performance metrics from projects
|
|
const projectsWithPerformance = projects.map(p => ({
|
|
id: p.id,
|
|
title: p.title,
|
|
lighthouse: ((p.performance as Record<string, unknown>)?.lighthouse as number) || 0,
|
|
loadTime: ((p.performance as Record<string, unknown>)?.loadTime as number) || 0,
|
|
fcp: ((p.performance as Record<string, unknown>)?.firstContentfulPaint as number) || 0,
|
|
lcp: ((p.performance as Record<string, unknown>)?.coreWebVitals as Record<string, unknown>)?.lcp as number || 0,
|
|
cls: ((p.performance as Record<string, unknown>)?.coreWebVitals as Record<string, unknown>)?.cls as number || 0
|
|
}));
|
|
|
|
const avgLighthouse = projectsWithPerformance.length > 0
|
|
? Math.round(projectsWithPerformance.reduce((sum, p) => sum + p.lighthouse, 0) / projectsWithPerformance.length)
|
|
: 0;
|
|
|
|
// Calculate bounce rate from page views
|
|
const pageViewsByIP = pageViews.reduce((acc, pv) => {
|
|
const ip = pv.ip || 'unknown';
|
|
if (!acc[ip]) acc[ip] = [];
|
|
acc[ip].push(pv);
|
|
return acc;
|
|
}, {} as Record<string, typeof pageViews>);
|
|
|
|
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
|
|
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;
|
|
|
|
// Calculate pages per session
|
|
const pagesPerSession = totalSessions > 0 ? (pageViews.length / totalSessions).toFixed(1) : '0';
|
|
|
|
// Calculate performance metrics
|
|
const performance = {
|
|
avgLighthouse: (() => {
|
|
const projectsWithPerf = projects.filter(p => {
|
|
const perf = (p.performance as Record<string, unknown>) || {};
|
|
return (perf.lighthouse as number || 0) > 0;
|
|
});
|
|
return projectsWithPerf.length > 0
|
|
? Math.round(projectsWithPerf.reduce((sum, p) => {
|
|
const perf = (p.performance as Record<string, unknown>) || {};
|
|
return sum + (perf.lighthouse as number || 0);
|
|
}, 0) / projectsWithPerf.length)
|
|
: 0;
|
|
})(),
|
|
totalViews: pageViews.length,
|
|
metrics: {
|
|
bounceRate,
|
|
avgSessionDuration: avgSessionDuration,
|
|
pagesPerSession: parseFloat(pagesPerSession),
|
|
newUsers: new Set(pageViews.map(pv => pv.ip).filter(Boolean)).size
|
|
},
|
|
pageViews: {
|
|
total: pageViews.length,
|
|
last24h: pageViews.filter(pv => {
|
|
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
return new Date(pv.timestamp) > dayAgo;
|
|
}).length,
|
|
last7d: pageViews.filter(pv => {
|
|
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
return new Date(pv.timestamp) > weekAgo;
|
|
}).length,
|
|
last30d: pageViews.filter(pv => {
|
|
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
return new Date(pv.timestamp) > monthAgo;
|
|
}).length
|
|
},
|
|
interactions: {
|
|
total: userInteractions.length,
|
|
last24h: userInteractions.filter(ui => {
|
|
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
return new Date(ui.timestamp) > dayAgo;
|
|
}).length,
|
|
last7d: userInteractions.filter(ui => {
|
|
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
return new Date(ui.timestamp) > weekAgo;
|
|
}).length,
|
|
last30d: userInteractions.filter(ui => {
|
|
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
return new Date(ui.timestamp) > monthAgo;
|
|
}).length
|
|
},
|
|
topPages: pageViews.reduce((acc, pv) => {
|
|
acc[pv.page] = (acc[pv.page] || 0) + 1;
|
|
return acc;
|
|
}, {} as Record<string, number>),
|
|
topInteractions: userInteractions.reduce((acc, ui) => {
|
|
acc[ui.type] = (acc[ui.type] || 0) + 1;
|
|
return acc;
|
|
}, {} as Record<string, number>)
|
|
};
|
|
|
|
return NextResponse.json(performance);
|
|
} catch (error) {
|
|
console.error('Performance analytics error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch performance data' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|