diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 7dade6a..09556d6 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -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 | null = null + +async function getFileHash(buffer: Buffer): Promise { + return createHash('sha256').update(buffer).digest('hex') +} + +async function buildHashCache(folder: string): Promise { + 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 { + await buildHashCache(folder) + const hash = await getFileHash(buffer) + return hashCache?.get(hash) || null +} + const MIME_TO_FOLDER: Record = { '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) } diff --git a/src/app/page.tsx b/src/app/page.tsx index 75e8a18..6993ca9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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, diff --git a/src/components/MemorySection.tsx b/src/components/MemorySection.tsx index d74a5fb..d0ae4ac 100644 --- a/src/components/MemorySection.tsx +++ b/src/components/MemorySection.tsx @@ -34,39 +34,56 @@ export default function MemorySection({ memories }: { memories: Memory[] }) {
- {memories.map((memory, i) => ( - - {/* Opening quote mark */} - { + const photos = memory.media_filenames ? memory.media_filenames.split(',').filter(Boolean) : [] + return ( + - ❝ - + {/* Opening quote mark */} + + ❝ + -

- {memory.title} -

+

+ {memory.title} +

-

- {memory.content} -

- -

- {formatDate(memory.created_at)} - {memory.author && ( - — {memory.author} + {/* Photos */} + {photos.length > 0 && ( +

+ {photos.map((filename, j) => ( + + ))} +
)} -

-
- ))} + +

+ {memory.content} +

+ +

+ {formatDate(memory.created_at)} + {memory.author && ( + — {memory.author} + )} +

+ + ) + })}
diff --git a/src/components/TimelineSection.tsx b/src/components/TimelineSection.tsx index a3fd1ab..748a464 100644 --- a/src/components/TimelineSection.tsx +++ b/src/components/TimelineSection.tsx @@ -197,7 +197,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { key={i} src={`/api/files/${filename.trim()}`} alt="" - className="w-full h-24 object-cover rounded-lg" + className="w-full max-h-40 object-contain rounded-lg bg-warm-brown/5" /> ))} @@ -310,7 +310,7 @@ export default function TimelineSection({ entries }: TimelineSectionProps) { key={i} src={`/api/files/${filename.trim()}`} alt="" - className="w-full h-48 object-cover rounded-lg" + className="w-full object-contain rounded-lg bg-warm-brown/5" /> ))} diff --git a/src/lib/types.ts b/src/lib/types.ts index 731fe0a..19d2589 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -3,6 +3,7 @@ export type Memory = { title: string content: string author: string | null + media_filenames: string | null created_at: string updated_at: string }