mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
refactor: eslint and prettier conflict will be resolved and formatted
This commit is contained in:
@@ -9,158 +9,169 @@ type RouteParams = { params: Promise<{ videoId: string }> };
|
||||
|
||||
// GET /api/watch/[videoId]/progress - Get watch progress for the current user
|
||||
export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
try {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return apiErrors.unauthorized('Authentication required');
|
||||
}
|
||||
try {
|
||||
const session = await auth();
|
||||
|
||||
const { videoId } = await params;
|
||||
|
||||
// Get the video and its active version (project access data pre-fetched in same query)
|
||||
const userId = session.user.id;
|
||||
const video = await db.video.findUnique({
|
||||
where: { id: videoId },
|
||||
include: {
|
||||
project: { include: projectAccessInclude(userId) },
|
||||
versions: {
|
||||
where: { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!video) {
|
||||
return apiErrors.notFound('Video');
|
||||
}
|
||||
|
||||
const access = computeProjectAccess(video.project, userId);
|
||||
|
||||
if (!access.hasAccess) {
|
||||
return apiErrors.forbidden('Access denied');
|
||||
}
|
||||
|
||||
const activeVersion = video.versions[0];
|
||||
if (!activeVersion) {
|
||||
return apiErrors.notFound('Video version');
|
||||
}
|
||||
|
||||
// Get watch progress for this user and version
|
||||
const progress = await db.watchProgress.findUnique({
|
||||
where: {
|
||||
userId_versionId: {
|
||||
userId: session.user.id,
|
||||
versionId: activeVersion.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse({
|
||||
progress: progress ? progress.progress : 0,
|
||||
duration: progress?.duration || activeVersion.duration || 0,
|
||||
percentage: progress?.percentage || 0,
|
||||
updatedAt: progress?.updatedAt || null,
|
||||
});
|
||||
} catch (error) {
|
||||
logError('Error fetching watch progress:', error);
|
||||
return apiErrors.internalError('Failed to fetch watch progress');
|
||||
if (!session?.user?.id) {
|
||||
return apiErrors.unauthorized('Authentication required');
|
||||
}
|
||||
|
||||
const { videoId } = await params;
|
||||
|
||||
// Get the video and its active version (project access data pre-fetched in same query)
|
||||
const userId = session.user.id;
|
||||
const video = await db.video.findUnique({
|
||||
where: { id: videoId },
|
||||
include: {
|
||||
project: { include: projectAccessInclude(userId) },
|
||||
versions: {
|
||||
where: { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!video) {
|
||||
return apiErrors.notFound('Video');
|
||||
}
|
||||
|
||||
const access = computeProjectAccess(video.project, userId);
|
||||
|
||||
if (!access.hasAccess) {
|
||||
return apiErrors.forbidden('Access denied');
|
||||
}
|
||||
|
||||
const activeVersion = video.versions[0];
|
||||
if (!activeVersion) {
|
||||
return apiErrors.notFound('Video version');
|
||||
}
|
||||
|
||||
// Get watch progress for this user and version
|
||||
const progress = await db.watchProgress.findUnique({
|
||||
where: {
|
||||
userId_versionId: {
|
||||
userId: session.user.id,
|
||||
versionId: activeVersion.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse({
|
||||
progress: progress ? progress.progress : 0,
|
||||
duration: progress?.duration || activeVersion.duration || 0,
|
||||
percentage: progress?.percentage || 0,
|
||||
updatedAt: progress?.updatedAt || null,
|
||||
});
|
||||
} catch (error) {
|
||||
logError('Error fetching watch progress:', error);
|
||||
return apiErrors.internalError('Failed to fetch watch progress');
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/watch/[videoId]/progress - Save watch progress for the current user
|
||||
export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
try {
|
||||
// Rate limit watch progress updates (30 per minute to allow pause + periodic + visibility changes)
|
||||
const limited = await rateLimit(request, 'watch-progress');
|
||||
if (limited) return limited;
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return apiErrors.unauthorized('Authentication required');
|
||||
}
|
||||
try {
|
||||
// Rate limit watch progress updates (30 per minute to allow pause + periodic + visibility changes)
|
||||
const limited = await rateLimit(request, 'watch-progress');
|
||||
if (limited) return limited;
|
||||
|
||||
const { videoId } = await params;
|
||||
const body = await request.json();
|
||||
const { progress, duration, versionId } = body;
|
||||
const session = await auth();
|
||||
|
||||
const MAX_VIDEO_SECONDS = 86_400; // 24 hours — reasonable upper bound for any video
|
||||
|
||||
if (typeof progress !== 'number' || !isFinite(progress) || progress < 0 || progress > MAX_VIDEO_SECONDS) {
|
||||
return apiErrors.badRequest('Invalid progress value');
|
||||
}
|
||||
|
||||
if (duration !== undefined && (typeof duration !== 'number' || !isFinite(duration) || duration < 0 || duration > MAX_VIDEO_SECONDS)) {
|
||||
return apiErrors.badRequest('Invalid duration value');
|
||||
}
|
||||
|
||||
if (versionId !== undefined && typeof versionId !== 'string') {
|
||||
return apiErrors.badRequest('Invalid versionId');
|
||||
}
|
||||
|
||||
// Always load the requested video and validate access before writing progress.
|
||||
// If versionId is provided, verify it belongs to this video; otherwise resolve active version.
|
||||
// Project access data is pre-fetched in the same query — no extra round-trips.
|
||||
const userId = session.user.id;
|
||||
const video = await db.video.findUnique({
|
||||
where: { id: videoId },
|
||||
include: {
|
||||
project: { include: projectAccessInclude(userId) },
|
||||
versions: {
|
||||
where: versionId ? { id: versionId } : { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!video) {
|
||||
return apiErrors.notFound('Video');
|
||||
}
|
||||
|
||||
const access = computeProjectAccess(video.project, userId);
|
||||
if (!access.hasAccess) {
|
||||
return apiErrors.forbidden('Access denied');
|
||||
}
|
||||
|
||||
const targetVersion = video.versions[0];
|
||||
if (!targetVersion) {
|
||||
return apiErrors.notFound('Video version');
|
||||
}
|
||||
|
||||
// Calculate percentage
|
||||
const safeDuration = duration || 0;
|
||||
const percentage = safeDuration > 0 ? Math.min(100, (progress / safeDuration) * 100) : 0;
|
||||
|
||||
// Client already filters tiny deltas (<2s) before sending — safe to upsert directly.
|
||||
const watchProgress = await db.watchProgress.upsert({
|
||||
where: {
|
||||
userId_versionId: {
|
||||
userId: session.user.id,
|
||||
versionId: targetVersion.id,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
progress,
|
||||
duration: safeDuration,
|
||||
percentage,
|
||||
},
|
||||
create: {
|
||||
userId: session.user.id,
|
||||
versionId: targetVersion.id,
|
||||
progress,
|
||||
duration: safeDuration,
|
||||
percentage,
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse({
|
||||
success: true,
|
||||
progress: watchProgress.progress,
|
||||
percentage: watchProgress.percentage,
|
||||
});
|
||||
} catch (error) {
|
||||
logError('Error saving watch progress:', error);
|
||||
return apiErrors.internalError('Failed to save watch progress');
|
||||
if (!session?.user?.id) {
|
||||
return apiErrors.unauthorized('Authentication required');
|
||||
}
|
||||
|
||||
const { videoId } = await params;
|
||||
const body = await request.json();
|
||||
const { progress, duration, versionId } = body;
|
||||
|
||||
const MAX_VIDEO_SECONDS = 86_400; // 24 hours — reasonable upper bound for any video
|
||||
|
||||
if (
|
||||
typeof progress !== 'number' ||
|
||||
!isFinite(progress) ||
|
||||
progress < 0 ||
|
||||
progress > MAX_VIDEO_SECONDS
|
||||
) {
|
||||
return apiErrors.badRequest('Invalid progress value');
|
||||
}
|
||||
|
||||
if (
|
||||
duration !== undefined &&
|
||||
(typeof duration !== 'number' ||
|
||||
!isFinite(duration) ||
|
||||
duration < 0 ||
|
||||
duration > MAX_VIDEO_SECONDS)
|
||||
) {
|
||||
return apiErrors.badRequest('Invalid duration value');
|
||||
}
|
||||
|
||||
if (versionId !== undefined && typeof versionId !== 'string') {
|
||||
return apiErrors.badRequest('Invalid versionId');
|
||||
}
|
||||
|
||||
// Always load the requested video and validate access before writing progress.
|
||||
// If versionId is provided, verify it belongs to this video; otherwise resolve active version.
|
||||
// Project access data is pre-fetched in the same query — no extra round-trips.
|
||||
const userId = session.user.id;
|
||||
const video = await db.video.findUnique({
|
||||
where: { id: videoId },
|
||||
include: {
|
||||
project: { include: projectAccessInclude(userId) },
|
||||
versions: {
|
||||
where: versionId ? { id: versionId } : { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!video) {
|
||||
return apiErrors.notFound('Video');
|
||||
}
|
||||
|
||||
const access = computeProjectAccess(video.project, userId);
|
||||
if (!access.hasAccess) {
|
||||
return apiErrors.forbidden('Access denied');
|
||||
}
|
||||
|
||||
const targetVersion = video.versions[0];
|
||||
if (!targetVersion) {
|
||||
return apiErrors.notFound('Video version');
|
||||
}
|
||||
|
||||
// Calculate percentage
|
||||
const safeDuration = duration || 0;
|
||||
const percentage = safeDuration > 0 ? Math.min(100, (progress / safeDuration) * 100) : 0;
|
||||
|
||||
// Client already filters tiny deltas (<2s) before sending — safe to upsert directly.
|
||||
const watchProgress = await db.watchProgress.upsert({
|
||||
where: {
|
||||
userId_versionId: {
|
||||
userId: session.user.id,
|
||||
versionId: targetVersion.id,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
progress,
|
||||
duration: safeDuration,
|
||||
percentage,
|
||||
},
|
||||
create: {
|
||||
userId: session.user.id,
|
||||
versionId: targetVersion.id,
|
||||
progress,
|
||||
duration: safeDuration,
|
||||
percentage,
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse({
|
||||
success: true,
|
||||
progress: watchProgress.progress,
|
||||
percentage: watchProgress.percentage,
|
||||
});
|
||||
} catch (error) {
|
||||
logError('Error saving watch progress:', error);
|
||||
return apiErrors.internalError('Failed to save watch progress');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user