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:
@@ -152,10 +152,12 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
return apiErrors.badRequest('Invalid source. Allowed values: original, compressed');
|
||||
}
|
||||
if (
|
||||
rawQuality !== null
|
||||
&& (!Number.isFinite(requestedQuality) || !BUNNY_ALLOWED_QUALITIES.has(requestedQuality))
|
||||
rawQuality !== null &&
|
||||
(!Number.isFinite(requestedQuality) || !BUNNY_ALLOWED_QUALITIES.has(requestedQuality))
|
||||
) {
|
||||
return apiErrors.badRequest('Invalid quality. Allowed values: 2160, 1440, 1080, 720, 480, 360, 240');
|
||||
return apiErrors.badRequest(
|
||||
'Invalid quality. Allowed values: 2160, 1440, 1080, 720, 480, 360, 240'
|
||||
);
|
||||
}
|
||||
if (rawQuality !== null && sourcePreference === 'original') {
|
||||
return apiErrors.badRequest('Quality cannot be used when source=original');
|
||||
|
||||
@@ -6,10 +6,7 @@ import { db } from '@/lib/db';
|
||||
import { cleanupBunnyStreamVideosBestEffort } from '@/lib/bunny-stream-cleanup';
|
||||
import { deleteMediaFilesBestEffort } from '@/lib/r2-cleanup';
|
||||
import { buildCleanupWarnings, logCleanupWarnings } from '@/lib/cleanup-warnings';
|
||||
import {
|
||||
canDeleteAssetForViewer,
|
||||
getVideoAssetAccessContext,
|
||||
} from '@/lib/video-assets';
|
||||
import { canDeleteAssetForViewer, getVideoAssetAccessContext } from '@/lib/video-assets';
|
||||
import { logError } from '@/lib/logger';
|
||||
|
||||
type RouteParams = { params: Promise<{ videoId: string; assetId: string }> };
|
||||
@@ -72,12 +69,16 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
|
||||
r2CleanupResult = await deleteMediaFilesBestEffort([asset.sourceUrl]);
|
||||
}
|
||||
|
||||
let bunnyCleanupResult: Awaited<ReturnType<typeof cleanupBunnyStreamVideosBestEffort>> | undefined;
|
||||
let bunnyCleanupResult:
|
||||
| Awaited<ReturnType<typeof cleanupBunnyStreamVideosBestEffort>>
|
||||
| undefined;
|
||||
if (asset.provider === VideoAssetProvider.BUNNY && asset.providerVideoId) {
|
||||
bunnyCleanupResult = await cleanupBunnyStreamVideosBestEffort([{
|
||||
providerId: 'bunny',
|
||||
videoId: asset.providerVideoId,
|
||||
}]);
|
||||
bunnyCleanupResult = await cleanupBunnyStreamVideosBestEffort([
|
||||
{
|
||||
providerId: 'bunny',
|
||||
videoId: asset.providerVideoId,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const cleanupInput = {
|
||||
|
||||
@@ -43,12 +43,18 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
|
||||
const shareSession = getShareSessionFromRequest(request, context.video.id);
|
||||
if (!context.viewerUserId) {
|
||||
const quotaError = await enforceGuestUploadQuota(request, context.video.id, 'bunny', shareSession?.token ?? null);
|
||||
const quotaError = await enforceGuestUploadQuota(
|
||||
request,
|
||||
context.video.id,
|
||||
'bunny',
|
||||
shareSession?.token ?? null
|
||||
);
|
||||
if (quotaError) return quotaError;
|
||||
}
|
||||
|
||||
const apiKey = process.env.BUNNY_STREAM_API_KEY;
|
||||
const libraryId = process.env.BUNNY_STREAM_LIBRARY_ID || process.env.NEXT_PUBLIC_BUNNY_STREAM_LIBRARY_ID;
|
||||
const libraryId =
|
||||
process.env.BUNNY_STREAM_LIBRARY_ID || process.env.NEXT_PUBLIC_BUNNY_STREAM_LIBRARY_ID;
|
||||
if (!apiKey || !libraryId) {
|
||||
return apiErrors.internalError('Bunny Stream is not configured correctly');
|
||||
}
|
||||
@@ -81,23 +87,29 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
|
||||
let uploadToken = '';
|
||||
if (context.viewerUserId) {
|
||||
uploadToken = createBunnyUploadToken({
|
||||
userId: context.viewerUserId,
|
||||
projectId: context.video.projectId,
|
||||
videoId: bunnyVideoId,
|
||||
}, 3600);
|
||||
uploadToken = createBunnyUploadToken(
|
||||
{
|
||||
userId: context.viewerUserId,
|
||||
projectId: context.video.projectId,
|
||||
videoId: bunnyVideoId,
|
||||
},
|
||||
3600
|
||||
);
|
||||
} else {
|
||||
const expectedContext = deriveGuestUploadContext(request, shareSession?.token ?? null);
|
||||
if (!expectedContext) {
|
||||
return apiErrors.forbidden('Missing trusted client IP header');
|
||||
}
|
||||
|
||||
uploadToken = createGuestUploadToken({
|
||||
projectId: context.video.projectId,
|
||||
videoId: context.video.id,
|
||||
intent: 'bunny',
|
||||
context: expectedContext,
|
||||
}, 3600);
|
||||
uploadToken = createGuestUploadToken(
|
||||
{
|
||||
projectId: context.video.projectId,
|
||||
videoId: context.video.id,
|
||||
intent: 'bunny',
|
||||
context: expectedContext,
|
||||
},
|
||||
3600
|
||||
);
|
||||
}
|
||||
|
||||
const response = successResponse({
|
||||
|
||||
@@ -25,7 +25,12 @@ import {
|
||||
sanitizeAssetDisplayName,
|
||||
} from '@/lib/video-assets';
|
||||
import { logError } from '@/lib/logger';
|
||||
import { enforceStorageQuota, reserveStorageQuota, releaseStorageReservation, PLAN_STORAGE_LIMIT_BYTES } from '@/lib/storage-quota';
|
||||
import {
|
||||
enforceStorageQuota,
|
||||
reserveStorageQuota,
|
||||
releaseStorageReservation,
|
||||
PLAN_STORAGE_LIMIT_BYTES,
|
||||
} from '@/lib/storage-quota';
|
||||
import { getCachedUserBunnyStorage } from '@/lib/admin-stats';
|
||||
import { isStripeFeatureEnabled } from '@/lib/feature-flags';
|
||||
|
||||
@@ -69,10 +74,7 @@ type YouTubeTitleCacheRecord = {
|
||||
const youtubeTitleCache = new Map<string, YouTubeTitleCacheRecord>();
|
||||
|
||||
function isAllowedBunnyMediaUrl(url: string): boolean {
|
||||
const allowedHosts = new Set<string>([
|
||||
'iframe.mediadelivery.net',
|
||||
'video.bunnycdn.com',
|
||||
]);
|
||||
const allowedHosts = new Set<string>(['iframe.mediadelivery.net', 'video.bunnycdn.com']);
|
||||
const bunnyCdnHostname = resolveServerBunnyCdnHostname();
|
||||
if (bunnyCdnHostname) {
|
||||
allowedHosts.add(bunnyCdnHostname);
|
||||
@@ -87,7 +89,11 @@ function isAllowedBunnyMediaUrl(url: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function shapeAssetForViewer(asset: AssetWithViewerFields, canExposeSource: boolean, canDelete: boolean) {
|
||||
function shapeAssetForViewer(
|
||||
asset: AssetWithViewerFields,
|
||||
canExposeSource: boolean,
|
||||
canDelete: boolean
|
||||
) {
|
||||
return {
|
||||
id: asset.id,
|
||||
videoId: asset.videoId,
|
||||
@@ -161,10 +167,12 @@ async function isFreshImageAttachment(url: string): Promise<AttachmentCheck> {
|
||||
if (!key) return { isFresh: false, sizeBytes: BigInt(0) };
|
||||
|
||||
try {
|
||||
const head = await r2Client.send(new HeadObjectCommand({
|
||||
Bucket: R2_BUCKET_NAME,
|
||||
Key: key,
|
||||
}));
|
||||
const head = await r2Client.send(
|
||||
new HeadObjectCommand({
|
||||
Bucket: R2_BUCKET_NAME,
|
||||
Key: key,
|
||||
})
|
||||
);
|
||||
if (!head.LastModified) return { isFresh: false, sizeBytes: BigInt(0) };
|
||||
const isFresh = Date.now() - head.LastModified.getTime() <= UNATTACHED_UPLOAD_TTL_MS;
|
||||
return { isFresh, sizeBytes: BigInt(head.ContentLength ?? 0) };
|
||||
@@ -178,10 +186,12 @@ async function isFreshAudioAttachment(url: string): Promise<AttachmentCheck> {
|
||||
if (!key) return { isFresh: false, sizeBytes: BigInt(0) };
|
||||
|
||||
try {
|
||||
const head = await r2Client.send(new HeadObjectCommand({
|
||||
Bucket: R2_BUCKET_NAME,
|
||||
Key: key,
|
||||
}));
|
||||
const head = await r2Client.send(
|
||||
new HeadObjectCommand({
|
||||
Bucket: R2_BUCKET_NAME,
|
||||
Key: key,
|
||||
})
|
||||
);
|
||||
if (!head.LastModified) return { isFresh: false, sizeBytes: BigInt(0) };
|
||||
const isFresh = Date.now() - head.LastModified.getTime() <= UNATTACHED_UPLOAD_TTL_MS;
|
||||
return { isFresh, sizeBytes: BigInt(head.ContentLength ?? 0) };
|
||||
@@ -201,7 +211,10 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
if (!context) return apiErrors.notFound('Video');
|
||||
if (!context.hasViewAccess) return apiErrors.forbidden('Access denied');
|
||||
|
||||
const requestedLimit = parsePaginationParam(request.nextUrl.searchParams.get('limit'), ASSET_LIST_DEFAULT_LIMIT);
|
||||
const requestedLimit = parsePaginationParam(
|
||||
request.nextUrl.searchParams.get('limit'),
|
||||
ASSET_LIST_DEFAULT_LIMIT
|
||||
);
|
||||
const requestedOffset = parsePaginationParam(request.nextUrl.searchParams.get('offset'), 0);
|
||||
const limit = Math.min(ASSET_LIST_MAX_LIMIT, Math.max(1, requestedLimit));
|
||||
const offset = requestedOffset;
|
||||
@@ -215,10 +228,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
const etag = `"assets:${videoId}:${limit}:${offset}:${includeDeleteMetadata ? 1 : 0}:${context.canDownloadAssets ? 1 : 0}:${assetsRevision._count.id}:${assetsRevision._max.updatedAt?.getTime() ?? 0}"`;
|
||||
const ifNoneMatch = request.headers.get('if-none-match');
|
||||
if (ifNoneMatch) {
|
||||
const matches = ifNoneMatch
|
||||
.split(',')
|
||||
.map(normalizeEtag)
|
||||
.includes(normalizeEtag(etag));
|
||||
const matches = ifNoneMatch.split(',').map(normalizeEtag).includes(normalizeEtag(etag));
|
||||
if (matches) {
|
||||
const notModified = new NextResponse(null, { status: 304 });
|
||||
notModified.headers.set('ETag', etag);
|
||||
@@ -254,12 +264,15 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
const pagedAssets = hasMore ? assets.slice(0, limit) : assets;
|
||||
|
||||
const response = successResponse({
|
||||
assets: pagedAssets.map((asset) => shapeAssetForViewer(
|
||||
asset,
|
||||
// R2_AUDIO proxy URLs have no auth gate — expose them to any viewer so guests can preview audio
|
||||
context.canDownloadAssets || (asset.provider === VideoAssetProvider.R2_AUDIO && context.hasViewAccess),
|
||||
includeDeleteMetadata ? canDeleteAssetForViewer(asset, context) : false
|
||||
)),
|
||||
assets: pagedAssets.map((asset) =>
|
||||
shapeAssetForViewer(
|
||||
asset,
|
||||
// R2_AUDIO proxy URLs have no auth gate — expose them to any viewer so guests can preview audio
|
||||
context.canDownloadAssets ||
|
||||
(asset.provider === VideoAssetProvider.R2_AUDIO && context.hasViewAccess),
|
||||
includeDeleteMetadata ? canDeleteAssetForViewer(asset, context) : false
|
||||
)
|
||||
),
|
||||
pagination: {
|
||||
limit,
|
||||
offset,
|
||||
@@ -378,7 +391,10 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
|
||||
providerVideoId = parsedSource.videoId;
|
||||
const youtubeTitle = await fetchYouTubeTitle(providerVideoId);
|
||||
displayName = sanitizeAssetDisplayName(requestedDisplayName, youtubeTitle || `YouTube ${providerVideoId}`);
|
||||
displayName = sanitizeAssetDisplayName(
|
||||
requestedDisplayName,
|
||||
youtubeTitle || `YouTube ${providerVideoId}`
|
||||
);
|
||||
sourceUrl = parsedSource.originalUrl;
|
||||
thumbnailUrl = getThumbnailUrl(parsedSource, 'large');
|
||||
kind = 'VIDEO';
|
||||
@@ -386,7 +402,8 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
|
||||
if (provider === VideoAssetProvider.BUNNY) {
|
||||
sourceUrl = typeof body?.sourceUrl === 'string' ? body.sourceUrl.trim() : '';
|
||||
providerVideoId = typeof body?.providerVideoId === 'string' ? body.providerVideoId.trim() : '';
|
||||
providerVideoId =
|
||||
typeof body?.providerVideoId === 'string' ? body.providerVideoId.trim() : '';
|
||||
const uploadToken = typeof body?.uploadToken === 'string' ? body.uploadToken.trim() : '';
|
||||
thumbnailUrl = typeof body?.thumbnailUrl === 'string' ? body.thumbnailUrl.trim() : null;
|
||||
|
||||
@@ -515,10 +532,15 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
thumbnailUrl,
|
||||
sizeBytes: assetSizeBytes,
|
||||
uploadedByUserId: context.viewerUserId,
|
||||
uploadedByGuestIdentityId: context.viewerUserId ? null : guestIdentity?.identityId ?? null,
|
||||
uploadedByGuestIdentityId: context.viewerUserId
|
||||
? null
|
||||
: (guestIdentity?.identityId ?? null),
|
||||
uploadedByGuestName: context.viewerUserId
|
||||
? null
|
||||
: sanitizeAssetDisplayName(typeof body?.guestName === 'string' ? body.guestName : null, 'Guest'),
|
||||
: sanitizeAssetDisplayName(
|
||||
typeof body?.guestName === 'string' ? body.guestName : null,
|
||||
'Guest'
|
||||
),
|
||||
billedUserId,
|
||||
},
|
||||
select: {
|
||||
@@ -540,11 +562,10 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
});
|
||||
});
|
||||
|
||||
const response = successResponse(shapeAssetForViewer(
|
||||
created,
|
||||
context.canDownloadAssets,
|
||||
true
|
||||
), 201);
|
||||
const response = successResponse(
|
||||
shapeAssetForViewer(created, context.canDownloadAssets, true),
|
||||
201
|
||||
);
|
||||
if (isGuest && guestIdentity?.shouldSetCookie) {
|
||||
setGuestIdentityCookie(response, guestIdentity.identityId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user