refactor: eslint and prettier conflict will be resolved and formatted

This commit is contained in:
Enes Köksal
2026-04-23 17:05:43 +03:00
parent 385b61f29b
commit 3cfea40fbd
219 changed files with 16638 additions and 13663 deletions
@@ -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({
+55 -34
View File
@@ -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);
}