a34d406375
- Add user contribution system (memories, timeline entries) - Add AI content moderation with Ollama (bad word detection + qwen3:4b) - Add family photo/video upload with admin approval - Add candle lighting feature - Add timeline and recipe sections - Add QR code page and OG image - Add site authentication (password-protected access) - Add proxy middleware for auth routing - Add admin dashboard for content management - Remove email fields, make name optional (default: Anonym) - Add CI/CD pipeline for Gitea Actions - Add Docker deployment configuration - Optimize Ollama RAM usage (42GB → 2.9GB) - Fix API routes accessibility through proxy middleware Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { writeFile, mkdir } from 'fs/promises'
|
|
import path from 'path'
|
|
import { cookies } from 'next/headers'
|
|
import { createHash, randomUUID } from 'crypto'
|
|
import { getDb } from '@/lib/db'
|
|
|
|
export const runtime = 'nodejs'
|
|
export const maxDuration = 60
|
|
|
|
async function isAdmin() {
|
|
const cookieStore = await cookies()
|
|
const token = cookieStore.get('admin_auth')?.value
|
|
const expected = createHash('sha256')
|
|
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
|
.digest('hex')
|
|
return token === expected
|
|
}
|
|
|
|
const DATA_DIR = path.resolve(process.cwd(), process.env.DATA_DIR || 'data')
|
|
|
|
const MIME_TO_FOLDER: Record<string, string> = {
|
|
'image/jpeg': 'photos',
|
|
'image/jpg': 'photos',
|
|
'image/png': 'photos',
|
|
'image/webp': 'photos',
|
|
'image/gif': 'photos',
|
|
'image/heic': 'photos',
|
|
'image/heif': 'photos',
|
|
'video/mp4': 'videos',
|
|
'video/quicktime': 'videos',
|
|
'video/mov': 'videos',
|
|
'video/x-msvideo': 'videos',
|
|
'audio/mpeg': 'music',
|
|
'audio/mp3': 'music',
|
|
'audio/mp4': 'music',
|
|
'audio/m4a': 'music',
|
|
'audio/aac': 'music',
|
|
'audio/wav': 'music',
|
|
'audio/x-wav': 'music',
|
|
}
|
|
|
|
const FOLDER_TO_TYPE: Record<string, 'photo' | 'video' | 'music'> = {
|
|
photos: 'photo',
|
|
videos: 'video',
|
|
music: 'music',
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const formData = await req.formData()
|
|
const files = formData.getAll('files') as File[]
|
|
const singleFile = formData.get('file') as File | null
|
|
|
|
// Support both 'file' (single) and 'files' (multiple)
|
|
const filesToProcess = files.length > 0 ? files : (singleFile ? [singleFile] : [])
|
|
|
|
if (filesToProcess.length === 0) {
|
|
return NextResponse.json({ error: 'Keine Dateien' }, { status: 400 })
|
|
}
|
|
|
|
const uploadedFiles = []
|
|
|
|
for (const file of filesToProcess) {
|
|
let mimeType = file.type?.toLowerCase() || ''
|
|
const ext = path.extname(file.name).toLowerCase()
|
|
|
|
if (!mimeType && (ext === '.heic' || ext === '.heif')) {
|
|
mimeType = 'image/heic'
|
|
}
|
|
|
|
const folder = MIME_TO_FOLDER[mimeType]
|
|
if (!folder) {
|
|
continue // Skip unsupported files
|
|
}
|
|
|
|
const filename = `${folder}/${randomUUID()}${ext || '.bin'}`
|
|
const filePath = path.join(DATA_DIR, 'uploads', filename)
|
|
|
|
await mkdir(path.dirname(filePath), { recursive: true })
|
|
const buffer = Buffer.from(await file.arrayBuffer())
|
|
await writeFile(filePath, buffer)
|
|
|
|
uploadedFiles.push(filename)
|
|
}
|
|
|
|
if (uploadedFiles.length === 0) {
|
|
return NextResponse.json({ error: 'Keine Dateien konnten verarbeitet werden' }, { status: 400 })
|
|
}
|
|
|
|
// Return array of filenames for multi-upload
|
|
return NextResponse.json({
|
|
filenames: uploadedFiles,
|
|
count: uploadedFiles.length
|
|
}, { status: 201 })
|
|
}
|