31dff10636
Sharp's prebuilt libvips lacks HEIF codec support. Replace with heic-convert (pure JS decoder) for reliable HEIC conversion on all platforms. Existing HEIC files on disk will be converted on-the-fly when served via /api/files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { writeFile, mkdir } from 'fs/promises'
|
|
import path from 'path'
|
|
import { randomUUID } from 'crypto'
|
|
import { getDb } from '@/lib/db'
|
|
import { convertHeicToJpeg } from '@/lib/heic'
|
|
|
|
export const runtime = 'nodejs'
|
|
export const maxDuration = 60
|
|
|
|
const DATA_DIR = path.resolve(process.cwd(), process.env.DATA_DIR || 'data')
|
|
|
|
const PHOTO_MIMES: Record<string, boolean> = {
|
|
'image/jpeg': true,
|
|
'image/jpg': true,
|
|
'image/png': true,
|
|
'image/webp': true,
|
|
'image/gif': true,
|
|
'image/heic': true,
|
|
'image/heif': true,
|
|
}
|
|
|
|
const VIDEO_MIMES: Record<string, boolean> = {
|
|
'video/mp4': true,
|
|
'video/quicktime': true,
|
|
'video/x-msvideo': true,
|
|
'video/webm': true,
|
|
}
|
|
|
|
// GET: List all pending/approved family uploads
|
|
export async function GET() {
|
|
const db = getDb()
|
|
const uploads = db
|
|
.prepare("SELECT * FROM media WHERE status IN ('pending', 'approved') ORDER BY created_at DESC")
|
|
.all()
|
|
return NextResponse.json(uploads)
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const formData = await req.formData()
|
|
const file = formData.get('file') as File | null
|
|
const name = formData.get('name') as string | null
|
|
const email = formData.get('email') as string | null
|
|
const relation = formData.get('relation') as string | null
|
|
|
|
if (!file) {
|
|
return NextResponse.json({ error: 'Datei erforderlich' }, { status: 400 })
|
|
}
|
|
|
|
let mimeType = file.type?.toLowerCase() || ''
|
|
const ext = path.extname(file.name).toLowerCase()
|
|
|
|
if (!mimeType && (ext === '.heic' || ext === '.heif')) {
|
|
mimeType = 'image/heic'
|
|
}
|
|
|
|
let type: 'photo' | 'video'
|
|
let uploadDir: string
|
|
|
|
if (PHOTO_MIMES[mimeType]) {
|
|
type = 'photo'
|
|
uploadDir = 'photos'
|
|
} else if (VIDEO_MIMES[mimeType]) {
|
|
type = 'video'
|
|
uploadDir = 'videos'
|
|
} else {
|
|
return NextResponse.json(
|
|
{ error: 'Nur Fotos und Videos erlaubt' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
const buffer = Buffer.from(await file.arrayBuffer())
|
|
|
|
// Convert HEIC/HEIF to JPEG so all browsers can display it
|
|
let finalBuffer: Buffer = buffer
|
|
let finalExt = ext
|
|
if (mimeType === 'image/heic' || mimeType === 'image/heif') {
|
|
try {
|
|
finalBuffer = await convertHeicToJpeg(buffer)
|
|
finalExt = '.jpg'
|
|
} catch {
|
|
// Conversion failed — keep original
|
|
}
|
|
}
|
|
|
|
const filename = `${uploadDir}/${randomUUID()}${finalExt || '.bin'}`
|
|
const filePath = path.join(DATA_DIR, 'uploads', filename)
|
|
|
|
await mkdir(path.dirname(filePath), { recursive: true })
|
|
await writeFile(filePath, finalBuffer)
|
|
|
|
// Build caption with uploader info
|
|
let caption = `Von ${(name || 'Anonym').trim()}`
|
|
if (relation?.trim()) caption += ` (${relation.trim()})`
|
|
|
|
const db = getDb()
|
|
const result = db
|
|
.prepare(
|
|
"INSERT INTO media (filename, original_name, type, caption, status) VALUES (?, ?, ?, ?, 'pending')"
|
|
)
|
|
.run(filename, file.name, type, caption)
|
|
|
|
const media = db
|
|
.prepare('SELECT * FROM media WHERE id = ?')
|
|
.get(result.lastInsertRowid)
|
|
|
|
return NextResponse.json(media, { status: 201 })
|
|
}
|