mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
feat: harden Bunny upload flow, migrate Bunny playback to hls.js, and add Bunny storage admin stats
This commit is contained in:
@@ -3,6 +3,7 @@ import { db } from '@/lib/db';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { ProjectMemberRole, WorkspaceMemberRole } from '@prisma/client';
|
||||
import { rateLimit } from '@/lib/rate-limit';
|
||||
import { cleanupBunnyStreamVideos } from '@/lib/bunny-stream-cleanup';
|
||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||
|
||||
type RouteParams = { params: Promise<{ projectId: string; videoId: string; versionId: string }> };
|
||||
@@ -125,6 +126,12 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
|
||||
|
||||
const wasActive = result.version.isActive;
|
||||
|
||||
// Delete Bunny provider asset for this version before DB deletion.
|
||||
await cleanupBunnyStreamVideos([{
|
||||
providerId: result.version.providerId,
|
||||
videoId: result.version.videoId,
|
||||
}]);
|
||||
|
||||
// Delete the version (cascades to comments)
|
||||
await db.videoVersion.delete({ where: { id: versionId } });
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { validateUrl, validateOptionalUrl } from '@/lib/validation';
|
||||
import { rateLimit } from '@/lib/rate-limit';
|
||||
import { notifyProjectOwner } from '@/lib/notifications';
|
||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||
import { verifyBunnyUploadToken } from '@/lib/bunny-upload-token';
|
||||
|
||||
type RouteParams = { params: Promise<{ projectId: string; videoId: string }> };
|
||||
|
||||
@@ -98,7 +99,16 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { videoUrl, providerId, providerVideoId, versionLabel, thumbnailUrl, duration, setActive } = body;
|
||||
const {
|
||||
videoUrl,
|
||||
providerId,
|
||||
providerVideoId,
|
||||
versionLabel,
|
||||
thumbnailUrl,
|
||||
duration,
|
||||
setActive,
|
||||
uploadToken
|
||||
} = body;
|
||||
|
||||
if (!videoUrl) {
|
||||
return apiErrors.badRequest('Video URL is required');
|
||||
@@ -115,6 +125,27 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
return apiErrors.badRequest(thumbnailUrlError);
|
||||
}
|
||||
|
||||
const normalizedProviderId = typeof providerId === 'string' && providerId.trim()
|
||||
? providerId.trim().toLowerCase()
|
||||
: 'youtube';
|
||||
const normalizedProviderVideoId = typeof providerVideoId === 'string' ? providerVideoId.trim() : '';
|
||||
const normalizedUploadToken = typeof uploadToken === 'string' ? uploadToken.trim() : '';
|
||||
|
||||
if (normalizedProviderId === 'bunny') {
|
||||
if (!normalizedProviderVideoId || !normalizedUploadToken) {
|
||||
return apiErrors.badRequest('Bunny uploads must include providerVideoId and uploadToken');
|
||||
}
|
||||
|
||||
const isValidUploadToken = verifyBunnyUploadToken(normalizedUploadToken, {
|
||||
userId: session.user.id,
|
||||
projectId,
|
||||
videoId: normalizedProviderVideoId,
|
||||
});
|
||||
if (!isValidUploadToken) {
|
||||
return apiErrors.forbidden('Invalid Bunny upload token');
|
||||
}
|
||||
}
|
||||
|
||||
const nextVersionNumber = (video.versions[0]?.versionNumber || 0) + 1;
|
||||
|
||||
// Use transaction to handle active flag
|
||||
@@ -131,8 +162,8 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
data: {
|
||||
versionNumber: nextVersionNumber,
|
||||
versionLabel: versionLabel?.trim() || null,
|
||||
providerId: providerId || 'youtube',
|
||||
videoId: providerVideoId || '',
|
||||
providerId: normalizedProviderId,
|
||||
videoId: normalizedProviderVideoId,
|
||||
originalUrl: videoUrl,
|
||||
title: versionLabel?.trim() || `Version ${nextVersionNumber}`,
|
||||
thumbnailUrl: thumbnailUrl || null,
|
||||
|
||||
Reference in New Issue
Block a user