Files
twotalesanimation c98b5c5692
Deploy Vault CMS / deploy (push) Failing after 5s
Load watch progress in one request on dashboard and player
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>
2026-11-11 17:48:35 +02:00

66 lines
2.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 });
}
}