feat: add bulk YouTube thumbnail download endpoint

POST /api/youtube/thumbnails/bulk (Super-Admin only)
Supports ?dryRun=true for preview. Downloads sequentially with 500ms delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-02-14 12:32:31 +00:00
parent 2872f32635
commit 6ffe6e756c

View file

@ -0,0 +1,94 @@
import { NextResponse, type NextRequest } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { downloadAndUploadImage } from '@/lib/utils/media-download'
import { getYouTubeThumbnail } from '@/lib/utils/youtube'
/**
* POST /api/youtube/thumbnails/bulk
*
* Bulk-Download von YouTube-Thumbnails für alle YouTubeContent-Einträge
* die noch kein Thumbnail haben. Erfordert Super-Admin-Rechte.
*
* Query-Parameter:
* - dryRun=true: Zeigt nur was passieren würde, ohne tatsächlich herunterzuladen
*/
export async function POST(req: NextRequest) {
try {
const payload = await getPayload({ config })
const { user } = await payload.auth({ headers: req.headers })
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const typedUser = user as { isSuperAdmin?: boolean }
if (!typedUser.isSuperAdmin) {
return NextResponse.json({ error: 'Super-Admin erforderlich' }, { status: 403 })
}
const dryRun = req.nextUrl.searchParams.get('dryRun') === 'true'
// Find all YouTubeContent without thumbnail that have a videoId
const contentWithoutThumbnail = await payload.find({
collection: 'youtube-content',
where: {
thumbnail: { exists: false },
'youtube.videoId': { exists: true },
},
limit: 500,
depth: 0,
})
const results = { processed: 0, downloaded: 0, skipped: 0, errors: 0, dryRun }
for (const doc of contentWithoutThumbnail.docs) {
results.processed++
const videoId = doc.youtube?.videoId
if (!videoId) {
results.skipped++
continue
}
if (dryRun) {
results.downloaded++
continue
}
try {
const thumbnailUrl = getYouTubeThumbnail(videoId, 'hq')
const filename = `yt-thumb-${videoId}.jpg`
const tenantId = typeof doc.tenant === 'object' ? doc.tenant?.id : doc.tenant
const mediaId = await downloadAndUploadImage(payload, {
url: thumbnailUrl,
filename,
alt: `Thumbnail: ${typeof doc.title === 'string' ? doc.title : doc.title?.de || videoId}`,
tenantId: tenantId || undefined,
})
if (mediaId) {
await payload.update({
collection: 'youtube-content',
id: doc.id,
data: { thumbnail: mediaId },
depth: 0,
})
results.downloaded++
} else {
results.errors++
}
} catch {
results.errors++
}
// Rate-limit delay (500ms between downloads)
await new Promise((resolve) => setTimeout(resolve, 500))
}
return NextResponse.json(results)
} catch (error) {
console.error('[yt-thumbnail-bulk] Error:', error)
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}
}