fix: photo display, duplicate detection, memory photos

- Remove duplicate FamilyUploadSection from public page
- Remove 'Von Anonym' caption from user-uploaded gallery photos
- Add SHA-256 duplicate detection in upload route (same file → same path)
- Fix timeline photos: use object-contain instead of object-cover (no clipping)
- Fix timeline modal photos: remove fixed h-48 height
- Add photo display support to MemorySection component
- Include media_filenames in memory contribution queries
- Add media_filenames to Memory type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
denshooter
2026-02-18 12:53:25 +01:00
parent 40ace3522c
commit 9223a2bfbb
5 changed files with 95 additions and 35 deletions
+42 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { writeFile, mkdir } from 'fs/promises'
import { writeFile, mkdir, readdir, readFile } from 'fs/promises'
import path from 'path'
import { cookies } from 'next/headers'
import { createHash, randomUUID } from 'crypto'
@@ -19,6 +19,35 @@ async function isAdmin() {
const DATA_DIR = path.resolve(process.cwd(), process.env.DATA_DIR || 'data')
// In-memory hash cache (populated on first use)
let hashCache: Map<string, string> | null = null
async function getFileHash(buffer: Buffer): Promise<string> {
return createHash('sha256').update(buffer).digest('hex')
}
async function buildHashCache(folder: string): Promise<void> {
if (hashCache) return
hashCache = new Map()
const uploadsDir = path.join(DATA_DIR, 'uploads', folder)
try {
const files = await readdir(uploadsDir)
for (const file of files) {
try {
const content = await readFile(path.join(uploadsDir, file))
const hash = await getFileHash(content)
hashCache.set(hash, `${folder}/${file}`)
} catch {}
}
} catch {}
}
async function findDuplicate(buffer: Buffer, folder: string): Promise<string | null> {
await buildHashCache(folder)
const hash = await getFileHash(buffer)
return hashCache?.get(hash) || null
}
const MIME_TO_FOLDER: Record<string, string> = {
'image/jpeg': 'photos',
'image/jpg': 'photos',
@@ -75,11 +104,22 @@ export async function POST(req: NextRequest) {
const filename = `${folder}/${randomUUID()}${ext || '.bin'}`
const filePath = path.join(DATA_DIR, 'uploads', filename)
const buffer = Buffer.from(await file.arrayBuffer())
// Check for duplicate
const existingFile = await findDuplicate(buffer, folder)
if (existingFile) {
uploadedFiles.push(existingFile)
continue
}
await mkdir(path.dirname(filePath), { recursive: true })
const buffer = Buffer.from(await file.arrayBuffer())
await writeFile(filePath, buffer)
// Add to hash cache
const hash = await getFileHash(buffer)
hashCache?.set(hash, filename)
uploadedFiles.push(filename)
}
+4 -2
View File
@@ -41,7 +41,7 @@ export default async function HomePage() {
try {
userMemories = plain(
db.prepare(`
SELECT id, name, title, content, created_at
SELECT id, name, title, content, media_filenames, created_at
FROM contributions
WHERE status = 'approved' AND type = 'memory'
ORDER BY created_at DESC
@@ -58,6 +58,8 @@ export default async function HomePage() {
id: m.id,
title: m.title || 'Erinnerung',
content: m.content,
author: m.name || null,
media_filenames: m.media_filenames || null,
created_at: m.created_at,
updated_at: m.created_at,
}))
@@ -144,7 +146,7 @@ export default async function HomePage() {
filename: filename.trim(),
original_name: null,
type: 'photo' as const,
caption: `Von ${c.name || 'Anonym'}`,
caption: null,
sort_order: 9998,
status: 'approved' as const,
created_at: c.created_at,