fix: uploads and contributions display
- Remove duplicate FamilyUploadSection (PhotoUploadSection already handles this) - Fix contributions POST: don't require content for timeline/media types - Save all fields (year, month, day, location, media_filenames) in contributions INSERT - Add user-uploaded photos from contributions to public photo gallery - Fix PhotoUploadSection to include title in submission Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -126,11 +126,25 @@ JSON:`
|
|||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { name, type, title, content, photoUrl, date, category } = body
|
const { name, type, title, content, year, month, day, location, media_filenames } = body
|
||||||
|
|
||||||
if (!content || !type) {
|
if (!type) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Content and type are required' },
|
{ error: 'Type is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require content for memory type, title for timeline
|
||||||
|
if (type === 'memory' && !content) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Content is required for memories' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (type === 'timeline' && !title) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Title is required for timeline entries' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -138,30 +152,35 @@ export async function POST(request: Request) {
|
|||||||
const db = getDb()
|
const db = getDb()
|
||||||
|
|
||||||
// 1. Check bad words instantly - clean content is auto-approved
|
// 1. Check bad words instantly - clean content is auto-approved
|
||||||
const badWordCheck = hasBadWords(content + ' ' + (title || ''))
|
const textToCheck = [content, title].filter(Boolean).join(' ')
|
||||||
|
const badWordCheck = textToCheck ? hasBadWords(textToCheck) : { flag: false }
|
||||||
const initialStatus = badWordCheck.flag ? 'flagged' : 'approved'
|
const initialStatus = badWordCheck.flag ? 'flagged' : 'approved'
|
||||||
const moderationReason = badWordCheck.flag ? badWordCheck.reason : null
|
const moderationReason = badWordCheck.flag ? (badWordCheck as any).reason : null
|
||||||
|
|
||||||
// 2. Insert contribution
|
// 2. Insert contribution with all fields
|
||||||
const result = db.prepare(`
|
const result = db.prepare(`
|
||||||
INSERT INTO contributions (name, type, title, content, status, moderation_reason)
|
INSERT INTO contributions (name, type, title, content, year, month, day, location, media_filenames, status, moderation_reason)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`).run(
|
`).run(
|
||||||
name || 'Anonym',
|
name || 'Anonym',
|
||||||
type,
|
type,
|
||||||
title || null,
|
title || null,
|
||||||
content,
|
content || null,
|
||||||
|
year || null,
|
||||||
|
month || null,
|
||||||
|
day || null,
|
||||||
|
location || null,
|
||||||
|
media_filenames || null,
|
||||||
initialStatus,
|
initialStatus,
|
||||||
moderationReason || null
|
moderationReason || null
|
||||||
)
|
)
|
||||||
|
|
||||||
const contributionId = Number(result.lastInsertRowid)
|
const contributionId = Number(result.lastInsertRowid)
|
||||||
console.log(`[API] Created contribution ${contributionId}, status: ${initialStatus}`)
|
console.log(`[API] Created contribution ${contributionId}, type: ${type}, status: ${initialStatus}`)
|
||||||
|
|
||||||
// 3. If not already flagged, run AI check in background
|
// 3. If not already flagged and has text, run AI check in background
|
||||||
if (!badWordCheck.flag) {
|
if (!badWordCheck.flag && textToCheck) {
|
||||||
// Fire and forget - don't await
|
moderateWithAI(contributionId, textToCheck).catch(e =>
|
||||||
moderateWithAI(contributionId, content).catch(e =>
|
|
||||||
console.error('[AI-Mod] Background error:', e)
|
console.error('[AI-Mod] Background error:', e)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -169,7 +188,7 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
id: contributionId,
|
id: contributionId,
|
||||||
message: 'Beitrag wurde gespeichert und wird geprüft'
|
message: 'Beitrag wurde gespeichert'
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+23
-7
@@ -11,7 +11,6 @@ import TimelineSection from '@/components/TimelineSection'
|
|||||||
import TimelineUploadSection from '@/components/TimelineUploadSection'
|
import TimelineUploadSection from '@/components/TimelineUploadSection'
|
||||||
import MemoryUploadSection from '@/components/MemoryUploadSection'
|
import MemoryUploadSection from '@/components/MemoryUploadSection'
|
||||||
import PhotoUploadSection from '@/components/PhotoUploadSection'
|
import PhotoUploadSection from '@/components/PhotoUploadSection'
|
||||||
import FamilyUploadSection from '@/components/FamilyUploadSection'
|
|
||||||
import RecipeSection from '@/components/RecipeSection'
|
import RecipeSection from '@/components/RecipeSection'
|
||||||
import RecipeUploadSection from '@/components/RecipeUploadSection'
|
import RecipeUploadSection from '@/components/RecipeUploadSection'
|
||||||
|
|
||||||
@@ -123,7 +122,7 @@ export default async function HomePage() {
|
|||||||
|
|
||||||
// Create virtual MediaItem entries for timeline photos
|
// Create virtual MediaItem entries for timeline photos
|
||||||
const timelinePhotos: MediaItem[] = Array.from(timelinePhotoFilenames).map((filename, i) => ({
|
const timelinePhotos: MediaItem[] = Array.from(timelinePhotoFilenames).map((filename, i) => ({
|
||||||
id: 999000 + i, // High ID to avoid conflicts
|
id: 999000 + i,
|
||||||
filename,
|
filename,
|
||||||
original_name: null,
|
original_name: null,
|
||||||
type: 'photo' as const,
|
type: 'photo' as const,
|
||||||
@@ -133,8 +132,28 @@ export default async function HomePage() {
|
|||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Merge with existing photos
|
// Fetch approved media contributions (user-uploaded photos)
|
||||||
const allPhotos = [...photos, ...timelinePhotos]
|
let mediaContribPhotos: MediaItem[] = []
|
||||||
|
try {
|
||||||
|
const mediaContribs = plain(
|
||||||
|
db.prepare("SELECT id, media_filenames, name, created_at FROM contributions WHERE status = 'approved' AND media_filenames IS NOT NULL AND media_filenames != ''").all()
|
||||||
|
)
|
||||||
|
mediaContribPhotos = mediaContribs.flatMap((c: any) =>
|
||||||
|
c.media_filenames.split(',').filter(Boolean).map((filename: string, i: number) => ({
|
||||||
|
id: 998000 + c.id * 10 + i,
|
||||||
|
filename: filename.trim(),
|
||||||
|
original_name: null,
|
||||||
|
type: 'photo' as const,
|
||||||
|
caption: `Von ${c.name || 'Anonym'}`,
|
||||||
|
sort_order: 9998,
|
||||||
|
status: 'approved' as const,
|
||||||
|
created_at: c.created_at,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// Merge all photos
|
||||||
|
const allPhotos = [...photos, ...timelinePhotos, ...mediaContribPhotos]
|
||||||
|
|
||||||
const recipes = plain<Recipe>(
|
const recipes = plain<Recipe>(
|
||||||
db.prepare('SELECT * FROM recipes ORDER BY sort_order, title').all()
|
db.prepare('SELECT * FROM recipes ORDER BY sort_order, title').all()
|
||||||
@@ -218,9 +237,6 @@ export default async function HomePage() {
|
|||||||
{/* Photo Upload */}
|
{/* Photo Upload */}
|
||||||
<PhotoUploadSection />
|
<PhotoUploadSection />
|
||||||
|
|
||||||
{/* Family Upload */}
|
|
||||||
<FamilyUploadSection />
|
|
||||||
|
|
||||||
{/* Memories */}
|
{/* Memories */}
|
||||||
<section id="erinnerungen">
|
<section id="erinnerungen">
|
||||||
<MemorySection memories={combinedMemories} />
|
<MemorySection memories={combinedMemories} />
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export default function PhotoUploadSection() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: 'Anonym',
|
name: 'Anonym',
|
||||||
type: 'media',
|
type: 'media',
|
||||||
|
title: 'Foto-Upload',
|
||||||
media_filenames: uploadedFilenames.join(','),
|
media_filenames: uploadedFilenames.join(','),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user