Initial commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// app/api/admin/stats/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth-options';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.email)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const role = (session as any)?.user?.role ?? null;
|
||||
if (!(role === 'admin' || role === 'superadmin')) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Fetch all videos with aggregated stats using raw queries for better performance
|
||||
const videos = await prisma.video.findMany({
|
||||
include: {
|
||||
playlist: {
|
||||
select: {
|
||||
title: true,
|
||||
},
|
||||
},
|
||||
uploader: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
likes: true,
|
||||
comments: true,
|
||||
unlocks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
// Fetch detailed stats for each video using raw queries for better performance
|
||||
const statsData = await prisma.$queryRaw<
|
||||
Array<{
|
||||
videoId: string;
|
||||
totalViewers: number;
|
||||
totalSecondsWatched: bigint;
|
||||
avgPercentWatched: number | null;
|
||||
totalSegments: number;
|
||||
completionCount: number;
|
||||
}>
|
||||
>`
|
||||
SELECT
|
||||
p."videoId",
|
||||
COUNT(DISTINCT p."userId") as "totalViewers",
|
||||
CAST(COALESCE(SUM(p."watchedSec"), 0) AS BIGINT) as "totalSecondsWatched",
|
||||
ROUND(CAST(AVG(p."percent") AS NUMERIC), 2) as "avgPercentWatched",
|
||||
(SELECT COUNT(*) FROM "VideoWatchSegment" ws WHERE ws."videoId" = p."videoId") as "totalSegments",
|
||||
COUNT(CASE WHEN p."completed" = true THEN 1 END) as "completionCount"
|
||||
FROM "VideoProgress" p
|
||||
GROUP BY p."videoId"
|
||||
`;
|
||||
|
||||
// Create a map for quick lookup
|
||||
const statsMap = new Map(statsData.map(item => [item.videoId, item]));
|
||||
|
||||
// Combine video data with stats
|
||||
const videosWithStats = videos.map(video => {
|
||||
const stats = statsMap.get(video.id);
|
||||
const views = video._count.unlocks || 0;
|
||||
const totalViewers = stats ? Number(stats.totalViewers) : 0;
|
||||
const completions = stats ? Number(stats.completionCount) : 0;
|
||||
const engagement = (video._count.likes || 0) + (video._count.comments || 0);
|
||||
const avgWatched = stats?.avgPercentWatched ? Number(stats.avgPercentWatched) : 0;
|
||||
|
||||
return {
|
||||
id: video.id,
|
||||
title: video.title,
|
||||
playlistTitle: video.playlist.title,
|
||||
uploaderName: video.uploader?.name || 'Unknown',
|
||||
views,
|
||||
totalViewers,
|
||||
completions,
|
||||
completionRate: totalViewers > 0 ? ((completions / totalViewers) * 100).toFixed(2) : '0.00',
|
||||
likes: video._count.likes,
|
||||
comments: video._count.comments,
|
||||
engagement,
|
||||
avgPercentWatched: avgWatched,
|
||||
totalSecondsWatched: stats ? Number(stats.totalSecondsWatched) : 0,
|
||||
totalSegments: stats ? Number(stats.totalSegments) : 0,
|
||||
durationSec: video.durationSec,
|
||||
createdAt: video.createdAt,
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json(videosWithStats);
|
||||
} catch (err: any) {
|
||||
console.error('get stats error', err);
|
||||
return NextResponse.json(
|
||||
{ error: err?.message ?? 'server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user