Initial commit: Maria Malejka memorial website
Next.js 14 + node:sqlite memorial site with: - Hero section, photo slideshow & gallery - Memory/thoughts editor (admin) - Music player with upload - Video gallery - Docker Compose deployment - Responsive warm earth tone design
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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
|
||||
|
||||
function isAdmin() {
|
||||
const token = cookies().get('admin_auth')?.value
|
||||
const expected = createHash('sha256')
|
||||
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
||||
.digest('hex')
|
||||
return token === expected
|
||||
}
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), '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) {
|
||||
if (!isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const formData = await req.formData()
|
||||
const file = formData.get('file') as File | null
|
||||
const caption = formData.get('caption') as string | null
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: 'Keine Datei' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Try to detect type from mime or extension
|
||||
let mimeType = file.type?.toLowerCase() || ''
|
||||
const ext = path.extname(file.name).toLowerCase()
|
||||
|
||||
// iOS HEIC fallback
|
||||
if (!mimeType && (ext === '.heic' || ext === '.heif')) {
|
||||
mimeType = 'image/heic'
|
||||
}
|
||||
|
||||
const folder = MIME_TO_FOLDER[mimeType]
|
||||
if (!folder) {
|
||||
return NextResponse.json(
|
||||
{ error: `Dateityp "${mimeType}" nicht unterstützt` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
const db = getDb()
|
||||
const result = db
|
||||
.prepare(
|
||||
'INSERT INTO media (filename, original_name, type, caption) VALUES (?, ?, ?, ?)'
|
||||
)
|
||||
.run(filename, file.name, FOLDER_TO_TYPE[folder], caption || null)
|
||||
|
||||
const media = db
|
||||
.prepare('SELECT * FROM media WHERE id = ?')
|
||||
.get(result.lastInsertRowid)
|
||||
return NextResponse.json(media, { status: 201 })
|
||||
}
|
||||
Reference in New Issue
Block a user