c98b5c5692
Deploy Vault CMS / deploy (push) Failing after 5s
Add GET /api/progress/all, which returns the signed-in user's progress and merged watch segments for every video (or one playlist) using two queries. - Dashboard: replaces two sequential requests per video with a single request on load - Player sidebar: replaces one request per playlist video with a single request per playlist Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
// app/api/progress/all/route.ts
|
||
// Everything the signed-in user has watched, in one response, so pages do not
|
||
// have to ask about each video separately.
|
||
import { NextResponse } from 'next/server';
|
||
import { prisma } from '@/lib/prisma';
|
||
import { getViewer } from '@/lib/video-access';
|
||
import { mergeRanges } from '@/lib/watch-progress';
|
||
|
||
type Segment = { startSec: number; endSec: number; watchedAt?: Date };
|
||
|
||
/**
|
||
* GET /api/progress/all – all of the user's progress
|
||
* GET /api/progress/all?playlistId – only videos in that playlist
|
||
*
|
||
* Returns { progress: { [videoId]: { percent, watchedSec, segments } } }.
|
||
* Videos the user has never played are simply absent.
|
||
*/
|
||
export async function GET(req: Request) {
|
||
try {
|
||
const viewer = await getViewer();
|
||
if (!viewer) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||
|
||
const playlistId = new URL(req.url).searchParams.get('playlistId');
|
||
const where = {
|
||
userId: viewer.id,
|
||
...(playlistId ? { video: { playlistId } } : {}),
|
||
};
|
||
|
||
const [progressRows, segmentRows] = await Promise.all([
|
||
prisma.videoProgress.findMany({
|
||
where,
|
||
select: { videoId: true, percent: true, watchedSec: true },
|
||
}),
|
||
prisma.videoWatchSegment.findMany({
|
||
where,
|
||
select: { videoId: true, startSec: true, endSec: true, watchedAt: true },
|
||
}),
|
||
]);
|
||
|
||
const segmentsByVideo = new Map<string, Segment[]>();
|
||
for (const { videoId, ...segment } of segmentRows) {
|
||
const list = segmentsByVideo.get(videoId);
|
||
if (list) list.push(segment);
|
||
else segmentsByVideo.set(videoId, [segment]);
|
||
}
|
||
|
||
const progress: Record<
|
||
string,
|
||
{ percent: number; watchedSec: number; segments: Segment[] }
|
||
> = {};
|
||
for (const row of progressRows) {
|
||
progress[row.videoId] = {
|
||
percent: row.percent,
|
||
watchedSec: row.watchedSec,
|
||
// Older data can hold many overlapping rows per video; send the merged view.
|
||
segments: mergeRanges(segmentsByVideo.get(row.videoId) ?? []),
|
||
};
|
||
}
|
||
|
||
return NextResponse.json({ progress });
|
||
} catch (err: any) {
|
||
console.error('progress/all GET error', err);
|
||
return NextResponse.json({ error: err?.message ?? 'server error' }, { status: 500 });
|
||
}
|
||
}
|