bring up to date with github

This commit is contained in:
Garret Patti
2026-04-05 10:01:34 -04:00
parent 1c3a0fe4ee
commit de8ba04bd3
14 changed files with 316 additions and 129 deletions

View File

@@ -0,0 +1,103 @@
import path from 'path'
import fs from 'fs'
import sharp from 'sharp'
import { NextRequest, NextResponse } from 'next/server'
import { getLibrary, updateLibraryCover, clearLibraryCover } from '@/lib/libraries'
const COVERS_DIR = path.resolve(process.cwd(), '.covers')
const MAX_COVER_BYTES = 10 * 1024 * 1024 // 10 MB
function coverPath(id: string, ext: string) {
return path.join(COVERS_DIR, `${id}.${ext}`)
}
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const library = getLibrary(id)
if (!library?.coverExt) {
return new NextResponse(null, { status: 404 })
}
const filePath = coverPath(id, library.coverExt)
try {
const data = fs.readFileSync(filePath)
return new NextResponse(data, {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=86400',
},
})
} catch {
return new NextResponse(null, { status: 404 })
}
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const library = getLibrary(id)
if (!library) {
return NextResponse.json({ error: 'Library not found' }, { status: 404 })
}
let formData: FormData
try {
formData = await request.formData()
} catch {
return NextResponse.json({ error: 'Invalid form data' }, { status: 400 })
}
const file = formData.get('cover')
if (!(file instanceof File)) {
return NextResponse.json({ error: 'cover file is required' }, { status: 400 })
}
if (file.size > MAX_COVER_BYTES) {
return NextResponse.json({ error: 'File too large. Maximum size is 10 MB.' }, { status: 400 })
}
const rawBuffer = Buffer.from(await file.arrayBuffer())
// Re-encode through sharp — validates it's a real image and strips metadata
let processedBuffer: Buffer
try {
processedBuffer = await sharp(rawBuffer).jpeg({ quality: 90 }).toBuffer()
} catch {
return NextResponse.json({ error: 'Invalid or corrupt image file.' }, { status: 400 })
}
fs.mkdirSync(COVERS_DIR, { recursive: true })
// Remove any existing cover (may have a different extension from older uploads)
if (library.coverExt) {
try { fs.unlinkSync(coverPath(id, library.coverExt)) } catch { /* ignore */ }
}
fs.writeFileSync(coverPath(id, 'jpg'), processedBuffer)
updateLibraryCover(id, 'jpg')
return NextResponse.json({ coverExt: 'jpg' }, { status: 200 })
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const library = getLibrary(id)
if (!library) {
return NextResponse.json({ error: 'Library not found' }, { status: 404 })
}
if (library.coverExt) {
try { fs.unlinkSync(coverPath(id, library.coverExt)) } catch { /* ignore */ }
clearLibraryCover(id)
}
return new NextResponse(null, { status: 204 })
}