diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index 09d0b7f..758a91f 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -8,23 +8,25 @@ on:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
-
+
steps:
- name: Checkout code
- uses: actions/checkout@v3
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
-
+ uses: actions/checkout@v4
+
+ - name: Create proxy network if needed
+ run: docker network create proxy || true
+
- name: Build Docker image
- run: |
- docker build -t oma-memorial:latest .
-
+ run: docker build -t oma-memorial:latest .
+
- name: Stop and remove old container
run: |
- docker stop oma-memorial || true
- docker rm oma-memorial || true
-
+ docker stop oma-memorial 2>/dev/null || true
+ docker rm oma-memorial 2>/dev/null || true
+
+ - name: Ensure data directory exists
+ run: mkdir -p ${{ gitea.workspace }}/data
+
- name: Run container in proxy network
run: |
docker run -d \
@@ -32,14 +34,28 @@ jobs:
--network proxy \
--restart unless-stopped \
-e NODE_ENV=production \
- -v $(pwd)/data:/app/data \
+ -e SITE_PASSWORD="${{ secrets.SITE_PASSWORD }}" \
+ -e ADMIN_PASSWORD="${{ secrets.ADMIN_PASSWORD }}" \
+ -v ${{ gitea.workspace }}/data:/app/data \
oma-memorial:latest
-
+
- name: Health check
run: |
- sleep 10
- docker exec oma-memorial curl -f http://localhost:3000 || exit 1
-
- - name: Show container logs
+ echo "Waiting for container to start..."
+ for i in $(seq 1 15); do
+ if docker exec oma-memorial wget -q --spider http://localhost:3000 2>/dev/null; then
+ echo "Container is healthy!"
+ exit 0
+ fi
+ echo "Attempt $i/15..."
+ sleep 2
+ done
+ echo "Health check failed"
+ docker logs oma-memorial --tail 30
+ exit 1
+
+ - name: Show container status
if: always()
- run: docker logs oma-memorial --tail 50
+ run: |
+ docker ps --filter name=oma-memorial
+ docker logs oma-memorial --tail 20
diff --git a/Dockerfile b/Dockerfile
index 9dc68a9..239ef79 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,7 +25,7 @@ COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
-RUN mkdir -p /data && chown nextjs:nodejs /data
+RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
USER nextjs
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 9984076..c125152 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -17,6 +17,8 @@ import {
Eye,
Loader2,
Flame,
+ User,
+ Heart,
} from 'lucide-react'
type Memory = {
@@ -749,9 +751,65 @@ export default function AdminPage() {
))}
)}
-
- {/* Candles Section */}
+ {/* User Memory Contributions */}
+ {timelineContributions.filter(c => c.type === 'memory').length > 0 && (
+
+
+
+ Nutzer-Erinnerungen ({timelineContributions.filter(c => c.type === 'memory').length})
+
+
+ {timelineContributions
+ .filter(c => c.type === 'memory')
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .map(c => {
+ const photos = c.media_filenames ? c.media_filenames.split(',').filter(Boolean) : []
+ return (
+
+
+
+
+ {c.title || 'Erinnerung'}
+ von {c.name}
+ {c.status === 'flagged' && 🚩}
+ {c.status === 'approved' && ✓}
+ {c.status === 'rejected' && ✗}
+
+ {c.moderation_reason && (
+
🚩 {c.moderation_reason}
+ )}
+
{c.content}
+ {photos.length > 0 && (
+
+ {photos.slice(0, 4).map((f, i) => (
+
}`})
+ ))}
+
+ )}
+
+
+ {(c.status === 'pending' || c.status === 'flagged') && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+ )
+ })}
+
+
+ )}
+
@@ -1118,9 +1176,50 @@ export default function AdminPage() {
))}
-
- {/* Recipes Section */}
+ {/* User Timeline Contributions */}
+ {timelineContributions.filter(c => c.type === 'timeline').length > 0 && (
+
+
+
+ Nutzer-Beiträge ({timelineContributions.filter(c => c.type === 'timeline').length})
+
+
+ {timelineContributions
+ .filter(c => c.type === 'timeline')
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .map(c => (
+
+
+
+ {c.title || 'Ohne Titel'}
+ {c.year && {c.day ? `${c.day}.` : ''}{c.month ? `${c.month}.` : ''}{c.year}}
+ {c.status === 'flagged' && 🚩}
+ {c.status === 'approved' && ✓}
+ {c.status === 'rejected' && ✗}
+
+
{c.name} {c.content ? `· ${c.content}` : ''}
+
+
+ {(c.status === 'pending' || c.status === 'flagged') && (
+ <>
+
+
+ >
+ )}
+
+
+
+ ))}
+
+
+ )}
+
@@ -1343,6 +1442,56 @@ export default function AdminPage() {
))
)}
+
+ {/* User Photo Contributions */}
+ {timelineContributions.filter(c => c.type === 'media' && c.media_filenames).length > 0 && (
+
+
+
+ Nutzer Foto-Uploads ({timelineContributions.filter(c => c.type === 'media' && c.media_filenames).length})
+
+
+ {timelineContributions
+ .filter(c => c.type === 'media' && c.media_filenames)
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .map(c => {
+ const photos = c.media_filenames ? c.media_filenames.split(',').filter(Boolean) : []
+ return (
+
+
+ {photos.slice(0, 3).map((f, i) => (
+
}`})
+ ))}
+ {photos.length > 3 &&
+{photos.length - 3}
}
+
+
+
+ {c.name}
+ {photos.length} Foto{photos.length > 1 ? 's' : ''}
+ {c.status === 'approved' && ✓}
+ {c.status === 'flagged' && 🚩}
+
+
{c.created_at ? new Date(c.created_at).toLocaleString('de-DE') : ''}
+
+
+ {(c.status === 'pending' || c.status === 'flagged') && (
+ <>
+
+
+ >
+ )}
+
+
+
+ )
+ })}
+
+
+ )}
{/* Contributions Section (New Unified) */}