feat(video-assets): add full video asset system (uploads, downloads, @mentions, and cleanup/billing integration)

This commit is contained in:
Yusuf İpek
2026-02-25 18:34:03 +03:00
parent 6eea327083
commit 9ce033d306
32 changed files with 3524 additions and 231 deletions
+31
View File
@@ -3,6 +3,7 @@ import { db } from '@/lib/db';
import { auth, checkProjectAccess } from '@/lib/auth';
import { rateLimit } from '@/lib/rate-limit';
import { cleanupProjectMediaFiles } from '@/lib/r2-cleanup';
import { cleanupBunnyStreamVideos } from '@/lib/bunny-stream-cleanup';
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
type RouteParams = { params: Promise<{ projectId: string }> };
@@ -157,6 +158,36 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
return apiErrors.forbidden('Only the project owner can delete it');
}
const [projectVersionRefs, projectAssetRefs] = await Promise.all([
db.videoVersion.findMany({
where: {
video: { projectId },
},
select: {
providerId: true,
videoId: true,
},
}),
db.videoAsset.findMany({
where: {
video: { projectId },
provider: 'BUNNY',
providerVideoId: { not: null },
},
select: {
providerVideoId: true,
},
}),
]);
await cleanupBunnyStreamVideos([
...projectVersionRefs,
...projectAssetRefs.map((asset) => ({
providerId: 'bunny',
videoId: asset.providerVideoId as string,
})),
]);
// Clean up voice files from R2 before cascade delete removes comment rows
await cleanupProjectMediaFiles(projectId);
@@ -126,6 +126,9 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
canManageTags: access.canEdit,
canResolveComments: access.canEdit,
canRequestApproval: access.canEdit,
canShareVideo: access.canEdit,
canUploadAssets: access.hasAccess,
canDownloadAssets: !!session?.user?.id && access.hasAccess,
});
return withCacheControl(response, 'private, no-cache');
@@ -212,6 +215,12 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
videoId: true,
},
},
assets: {
select: {
provider: true,
providerVideoId: true,
},
},
project: true,
},
});
@@ -226,7 +235,15 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
}
// Delete Bunny provider videos first to avoid orphaned assets.
await cleanupBunnyStreamVideos(video.versions);
await cleanupBunnyStreamVideos([
...video.versions,
...video.assets
.filter((asset) => asset.provider === 'BUNNY' && !!asset.providerVideoId)
.map((asset) => ({
providerId: 'bunny',
videoId: asset.providerVideoId as string,
})),
]);
// Clean up voice files from R2 before cascade delete removes comment rows
await cleanupVideoMediaFiles(videoId);