import { NextResponse } from 'next/server'; import { requireRole } from '@/lib/auth'; import { db } from '@/lib/db'; import sharp from 'sharp'; export const maxDuration = 300; // 5 minutes for large migrations async function convertToWebP(base64Data: string): Promise { const matches = base64Data.match(/^data:image\/(\w+);base64,(.+)$/); if (!matches) { return base64Data; } const buffer = Buffer.from(matches[2], 'base64'); const webpBuffer = await sharp(buffer) .webp({ quality: 90 }) .toBuffer(); return `data:image/webp;base64,${webpBuffer.toString('base64')}`; } export async function POST() { try { await requireRole('admin'); console.log('Starting WebP migration...'); const { rows: images } = await db.query( 'SELECT id, filename, data, thumbnail, medium FROM images' ); console.log(`Found ${images.length} images to process`); let converted = 0; let skipped = 0; let errors: number[] = []; let totalSavedKB = 0; for (const img of images) { try { let needsUpdate = false; const updates: { data?: string; thumbnail?: string; medium?: string } = {}; if (img.data && !img.data.includes('image/webp')) { console.log(`Converting image ${img.id}: ${img.filename} (full)`); updates.data = await convertToWebP(img.data); needsUpdate = true; } if (img.thumbnail && !img.thumbnail.includes('image/webp')) { console.log(`Converting image ${img.id}: ${img.filename} (thumbnail)`); updates.thumbnail = await convertToWebP(img.thumbnail); needsUpdate = true; } if (img.medium && !img.medium.includes('image/webp')) { console.log(`Converting image ${img.id}: ${img.filename} (medium)`); updates.medium = await convertToWebP(img.medium); needsUpdate = true; } if (needsUpdate) { const oldSize = (img.data?.length || 0) + (img.thumbnail?.length || 0) + (img.medium?.length || 0); const newSize = (updates.data?.length || img.data?.length || 0) + (updates.thumbnail?.length || img.thumbnail?.length || 0) + (updates.medium?.length || img.medium?.length || 0); const savedKB = Math.round((oldSize - newSize) / 1024); totalSavedKB += savedKB; await db.query( 'UPDATE images SET data = $1, thumbnail = $2, medium = $3 WHERE id = $4', [updates.data || img.data, updates.thumbnail || img.thumbnail, updates.medium || img.medium, img.id] ); converted++; } else { skipped++; } } catch (err) { console.error(`Error processing image ${img.id}:`, err); errors.push(img.id); } } return NextResponse.json({ success: true, total: images.length, converted, skipped, errors, totalSavedMB: Math.round(totalSavedKB / 1024 * 10) / 10, }); } catch (error) { console.error('Migration error:', error); if ((error as Error).message === 'Unauthorized') { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } return NextResponse.json({ error: 'Migration failed', details: (error as Error).message }, { status: 500 }); } } export async function GET() { try { await requireRole('admin'); const { rows: images } = await db.query( 'SELECT id, filename, LENGTH(data) as data_size, LENGTH(thumbnail) as thumb_size, LENGTH(medium) as medium_size FROM images' ); let totalSize = 0; let webpCount = 0; let jpegCount = 0; for (const img of images) { totalSize += (img.data_size || 0) + (img.thumb_size || 0) + (img.medium_size || 0); if (img.data_size) { // We'd need to fetch the actual data to check format, so estimate based on size } } return NextResponse.json({ totalImages: images.length, totalSizeKB: Math.round(totalSize / 1024), totalSizeMB: Math.round(totalSize / 1024 / 1024 * 10) / 10, }); } catch (error) { if ((error as Error).message === 'Unauthorized') { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } return NextResponse.json({ error: 'Failed to get stats' }, { status: 500 }); } }