feat: Add admin dashboard for platform statistics and newsletter management.

This commit is contained in:
Timo
2026-01-07 13:51:26 +01:00
parent 2a057ae3e3
commit 0774ff6f03
2 changed files with 678 additions and 186 deletions

View File

@@ -0,0 +1,216 @@
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { db } from '@/lib/db';
export async function GET(request: NextRequest) {
try {
// Check newsletter-admin cookie authentication
const cookieStore = cookies();
const adminCookie = cookieStore.get('newsletter-admin');
if (!adminCookie || adminCookie.value !== 'authenticated') {
return NextResponse.json(
{ error: 'Unauthorized - Admin login required' },
{ status: 401 }
);
}
// Get 30 days ago date
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
// Get 7 days ago date
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
// Get start of current month
const startOfMonth = new Date();
startOfMonth.setDate(1);
startOfMonth.setHours(0, 0, 0, 0);
// Fetch all statistics in parallel
const [
totalUsers,
premiumUsers,
newUsersThisWeek,
newUsersThisMonth,
totalQRCodes,
dynamicQRCodes,
staticQRCodes,
totalScans,
dynamicQRCodesWithScans,
activeQRCodes,
newsletterSubscribers,
] = await Promise.all([
// Total users
db.user.count(),
// Premium users (PRO or BUSINESS)
db.user.count({
where: {
plan: {
in: ['PRO', 'BUSINESS'],
},
},
}),
// New users this week
db.user.count({
where: {
createdAt: {
gte: sevenDaysAgo,
},
},
}),
// New users this month
db.user.count({
where: {
createdAt: {
gte: startOfMonth,
},
},
}),
// Total QR codes
db.qRCode.count(),
// Dynamic QR codes
db.qRCode.count({
where: {
type: 'DYNAMIC',
},
}),
// Static QR codes
db.qRCode.count({
where: {
type: 'STATIC',
},
}),
// Total scans
db.qRScan.count(),
// Get all dynamic QR codes with their scan counts
db.qRCode.findMany({
where: {
type: 'DYNAMIC',
},
include: {
_count: {
select: {
scans: true,
},
},
},
}),
// Active QR codes (scanned in last 30 days)
db.qRCode.findMany({
where: {
scans: {
some: {
ts: {
gte: thirtyDaysAgo,
},
},
},
},
distinct: ['id'],
}),
// Newsletter subscribers
db.newsletterSubscription.count({
where: {
status: 'subscribed',
},
}),
]);
// Calculate dynamic QR scans
const dynamicQRScans = dynamicQRCodesWithScans.reduce(
(total, qr) => total + qr._count.scans,
0
);
// Calculate average scans per dynamic QR
const avgScansPerDynamicQR =
dynamicQRCodes > 0 ? (dynamicQRScans / dynamicQRCodes).toFixed(1) : '0';
// Get top 5 most scanned QR codes
const topQRCodes = await db.qRCode.findMany({
take: 5,
include: {
_count: {
select: {
scans: true,
},
},
user: {
select: {
email: true,
name: true,
},
},
},
orderBy: {
scans: {
_count: 'desc',
},
},
});
// Get recent users
const recentUsers = await db.user.findMany({
take: 5,
orderBy: {
createdAt: 'desc',
},
select: {
email: true,
name: true,
plan: true,
createdAt: true,
},
});
return NextResponse.json({
users: {
total: totalUsers,
premium: premiumUsers,
newThisWeek: newUsersThisWeek,
newThisMonth: newUsersThisMonth,
recent: recentUsers,
},
qrCodes: {
total: totalQRCodes,
dynamic: dynamicQRCodes,
static: staticQRCodes,
active: activeQRCodes.length,
},
scans: {
total: totalScans,
dynamicOnly: dynamicQRScans,
avgPerDynamicQR: avgScansPerDynamicQR,
},
newsletter: {
subscribers: newsletterSubscribers,
},
topQRCodes: topQRCodes.map((qr) => ({
id: qr.id,
title: qr.title,
type: qr.type,
scans: qr._count.scans,
owner: qr.user.name || qr.user.email,
createdAt: qr.createdAt,
})),
});
} catch (error) {
console.error('Error fetching admin stats:', error);
return NextResponse.json(
{ error: 'Failed to fetch statistics' },
{ status: 500 }
);
}
}