Project-Image-Uploader/README.dev.md
matthias.lotz e4ddd229b8 feat: Public/Internal Host Separation
Implemented subdomain-based feature separation for production deployment.

**Backend:**
- New hostGate middleware for host-based API protection
- Public host blocks: /api/admin, /api/groups, /api/slideshow, /api/auth
- Public host allows: /api/upload, /api/manage, /api/social-media/platforms
- Rate limiting: 20 uploads/hour on public host (publicUploadLimiter)
- Audit log enhancement: source_host, source_type tracking
- Database migration 009: Added source tracking columns

**Frontend:**
- Host detection utility (hostDetection.js) with feature flags
- React code splitting with lazy loading for internal features
- Conditional routing: Internal routes only mounted on internal host
- 404 page: Host-specific messaging and navbar
- Clipboard fallback for HTTP environments

**Configuration:**
- Environment variables: PUBLIC_HOST, INTERNAL_HOST, ENABLE_HOST_RESTRICTION
- Docker dev setup: HOST variables, TRUST_PROXY_HOPS configuration
- Frontend .env.development: DANGEROUSLY_DISABLE_HOST_CHECK for Webpack

**Testing:**
- 20/20 hostGate unit tests passing
- Local testing guide in README.dev.md
- /etc/hosts setup for public.test.local, internal.test.local

**Bug Fixes:**
- Fixed clipboard API not available on HTTP
- Fixed missing PUBLIC_HOST in frontend env-config.js
- Fixed wrong navbar on 404 page for public host
- Fixed social media platforms loading in UUID management

**Documentation:**
- CHANGELOG.md: Complete feature documentation
- README.md: Feature overview
- README.dev.md: Host-separation testing guide
- TESTING-HOST-SEPARATION.md: Integration note
2025-11-25 22:02:53 +01:00

613 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Development Setup
## ⚠️ Wichtige Hinweise für Frontend-Entwickler
### 🔴 BREAKING CHANGES - API-Umstrukturierung (November 2025)
Im Rahmen der OpenAPI-Auto-Generation wurden **massive Änderungen** an der API-Struktur vorgenommen:
- **Authentication**: Admin-Endpoints laufen jetzt über serverseitige Sessions + CSRF Tokens
- **Route-Struktur**: Einige Pfade haben sich geändert (Single Source of Truth: `routeMappings.js`)
- **Error Handling**: Neue HTTP-Status-Codes (403 für Auth-Fehler)
**📖 Siehe:**
- **`frontend/MIGRATION-GUIDE.md`** - Detaillierte Migrations-Anleitung für Frontend
- **`backend/src/routes/README.md`** - Vollständige API-Route-Dokumentation
- **`AUTHENTICATION.md`** - Auth-System-Setup und Verwendung
---
## Schnellstart
### Starten (Development Environment)
```bash
# Mit Script (empfohlen):
./dev.sh
# Oder manuell:
docker compose -f docker/dev/docker-compose.yml up -d
```
### Zugriff
- **Frontend**: http://localhost:3000 (Hot Module Reloading aktiv)
- **Backend**: http://localhost:5001 (API)
- **API Documentation**: http://localhost:5001/api/docs/ (Swagger UI, nur in Development verfügbar)
- **Slideshow**: http://localhost:3000/slideshow
- **Moderation**: http://localhost:3000/moderation (Login über Admin Session)
### Logs verfolgen
```bash
# Alle Services:
docker compose -f docker/dev/docker-compose.yml logs -f
# Nur Frontend:
docker compose -f docker/dev/docker-compose.yml logs -f frontend-dev
# Nur Backend:
docker compose -f docker/dev/docker-compose.yml logs -f backend-dev
```
## API-Entwicklung
### ⚠️ BREAKING CHANGES - Frontend Migration erforderlich
**Massive API-Änderungen im November 2025:**
- Session + CSRF Authentication für alle Admin-Endpoints
- Route-Pfade umstrukturiert (siehe `routeMappings.js`)
- Neue Error-Response-Formate
**📖 Frontend Migration Guide**: `frontend/MIGRATION-GUIDE.md`
### Route-Struktur
Die API verwendet eine **Single Source of Truth** für Route-Mappings:
📄 **`backend/src/routes/routeMappings.js`** - Zentrale Route-Konfiguration
Siehe auch: **`backend/src/routes/README.md`** für vollständige API-Übersicht
**Wichtige Route-Gruppen:**
- `/api/upload`, `/api/download` - Öffentliche Upload/Download-Endpoints
- `/api/manage/:token` - Self-Service Management Portal (UUID-Token)
- `/api/admin/*` - Admin-Endpoints (Session + CSRF Authentication)
- `/api/system/migration/*` - Datenbank-Migrationen
**⚠️ Express Route-Reihenfolge beachten:**
Router mit spezifischen Routes **vor** generischen Routes mounten!
```javascript
// ✅ RICHTIG: Spezifisch vor generisch
{ router: 'consent', prefix: '/api/admin' }, // /groups/by-consent
{ router: 'admin', prefix: '/api/admin' }, // /groups/:groupId
// ❌ FALSCH: Generisch fängt alles ab
{ router: 'admin', prefix: '/api/admin' }, // /groups/:groupId matched auf 'by-consent'!
{ router: 'consent', prefix: '/api/admin' }, // Wird nie erreicht
```
### Authentication
**Zwei Auth-Systeme parallel:**
1. **Admin API (Session + CSRF)**:
```bash
# .env konfigurieren:
ADMIN_SESSION_SECRET=$(openssl rand -hex 32)
# Initialen Admin anlegen (falls benötigt)
curl -c cookies.txt http://localhost:5001/auth/setup/status
curl -X POST -H "Content-Type: application/json" \
-c cookies.txt -b cookies.txt \
-d '{"username":"admin","password":"SuperSicher123"}' \
http://localhost:5001/auth/setup/initial-admin
# Login + CSRF Token holen
curl -X POST -H "Content-Type: application/json" \
-c cookies.txt -b cookies.txt \
-d '{"username":"admin","password":"SuperSicher123"}' \
http://localhost:5001/auth/login
CSRF=$(curl -sb cookies.txt http://localhost:5001/auth/csrf-token | jq -r '.csrfToken')
# Authentifizierter Admin-Request
curl -b cookies.txt -H "X-CSRF-Token: $CSRF" \
http://localhost:5001/api/admin/groups
```
2. **Management Portal (UUID Token)**:
User, die Bilder hochladen, erhalten automatisch einen UUID-Token für das Self-Service Management Portal.
Über diesen Token / Link können sie ihre hochgeladenen Gruppen verwalten:
```bash
# Automatisch beim Upload generiert
GET /api/manage/550e8400-e29b-41d4-a716-446655440000
```
📖 **Vollständige Doku**: `AUTHENTICATION.md`
#### Admin-Hinweise: Logout & neue Nutzer
- **Logout:** Der Moderationsbereich enthält jetzt einen Logout-Button (Icon in der Kopfzeile). Klick → `POST /auth/logout` → Session beendet, Login erscheint erneut. Für Skripte kannst du weiterhin `curl -b cookies.txt -X POST http://localhost:5001/auth/logout` verwenden.
- **Weiterer Admin:** Verwende das neue API-basierte Skript `./scripts/create_admin_user.sh --server http://localhost:5001 --username zweiteradmin --password 'SuperPasswort123!' [--admin-user bestehend --admin-password ... --role ... --require-password-change]`. Das Skript erledigt Login, CSRF, Duplikats-Check und legt zusätzliche Admins über `/api/admin/users` an (Fallback: `backend/src/scripts/createAdminUser.js`).
### OpenAPI-Spezifikation
Die OpenAPI-Spezifikation wird **automatisch beim Backend-Start** generiert:
```bash
# Generiert: backend/docs/openapi.json
# Swagger UI: http://localhost:5001/api/docs/
# Manuelle Generierung:
cd backend
node src/generate-openapi.js
```
**Swagger-Annotationen in Routes:**
```javascript
router.get('/example', async (req, res) => {
/*
#swagger.tags = ['Example']
#swagger.summary = 'Get example data'
#swagger.responses[200] = { description: 'Success' }
*/
});
```
## Entwicklung
### Frontend-Entwicklung
- Code in `frontend/src/` editieren → Hot Module Reload übernimmt Änderungen
- Volumes: Source-Code wird live in Container gemountet
- Container-Namen: `image-uploader-frontend-dev`
**Wichtige Komponenten:**
- `Components/Pages/MultiUploadPage.js` - Upload-Interface mit Consent-Management
- `Components/ComponentUtils/MultiUpload/ConsentCheckboxes.js` - GDPR-konforme Consent-UI
- `Components/Pages/ModerationGroupsPage.js` - Moderation mit Consent-Filtern
- `services/reorderService.js` - Drag-and-Drop Logik
- `hooks/useImagePreloader.js` - Slideshow-Preloading
### Backend-Entwicklung
- Code in `backend/src/` editieren → Nodemon übernimmt Änderungen automatisch
- Container-Namen: `image-uploader-backend-dev`
- Environment: `NODE_ENV=development`
**Wichtige Module:**
- `routes/routeMappings.js` - Single Source of Truth für Route-Konfiguration
- `repositories/GroupRepository.js` - Consent-Management & CRUD
- `repositories/SocialMediaRepository.js` - Plattform- & Consent-Verwaltung
- `routes/batchUpload.js` - Upload mit Consent-Validierung
- `middlewares/session.js` - Express-Session + SQLite Store
- `middlewares/auth.js` - Admin Session-Guard & CSRF-Pflicht
- `database/DatabaseManager.js` - Automatische Migrationen
- `services/GroupCleanupService.js` - 7-Tage-Cleanup-Logik
### Datenbank-Entwicklung
#### Migrationen erstellen
```bash
# Neue Migration anlegen:
touch backend/src/database/migrations/XXX_description.sql
# Migrationen werden automatisch beim Backend-Start ausgeführt
# Manuell: docker compose -f docker/dev/docker-compose.yml restart backend-dev
```
#### Datenbank-Zugriff
```bash
# SQLite Shell:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db
# Schnellabfragen:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db "SELECT * FROM groups LIMIT 5;"
# Schema anzeigen:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db ".schema"
```
#### Migrationen debuggen
```bash
# Migration-Status prüfen:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db "SELECT * FROM schema_migrations;"
# Backend-Logs mit Migration-Output:
docker compose -f docker/dev/docker-compose.yml logs backend-dev | grep -i migration
```
### Konfiguration anpassen
- **Frontend**: `docker/dev/frontend/config/.env`
- **Backend**: `docker/dev/backend/config/.env`
- **Nginx**: `docker/dev/frontend/nginx.conf`
## Testing
### Automatisierte Tests
Das Backend verfügt über eine umfassende Test-Suite mit 45 Tests:
```bash
# Alle Tests ausführen:
cd backend
npm test
# Einzelne Test-Suite:
npm test -- tests/api/admin.test.js
# Mit Coverage-Report:
npm test -- --coverage
# Watch-Mode (während Entwicklung):
npm test -- --watch
```
**Test-Struktur:**
- `tests/unit/` - Unit-Tests (z.B. Auth-Middleware)
- `tests/api/` - Integration-Tests (API-Endpoints)
- `tests/setup.js` - Globale Test-Konfiguration
- `tests/testServer.js` - Test-Server-Helper
**Test-Features:**
- Jest + Supertest Framework
- In-Memory SQLite Database (isoliert)
- Singleton Server Pattern (schnell)
- 100% Test-Success-Rate (45/45 passing)
- ~10 Sekunden Ausführungszeit
- Coverage: 26% Statements, 15% Branches
**Test-Umgebung:**
- Verwendet `/tmp/test-image-uploader/` für Upload-Tests
- Eigene Datenbank `:memory:` (kein Konflikt mit Dev-DB)
- Environment: `NODE_ENV=test`
- Automatisches Cleanup nach Test-Run
**Neue Tests hinzufügen:**
```javascript
// tests/api/example.test.js
const { getRequest } = require('../testServer');
describe('Example API', () => {
it('should return 200', async () => {
const response = await getRequest()
.get('/api/example')
.expect(200);
expect(response.body).toHaveProperty('data');
});
});
```
### Manuelles Testing
### Consent-System testen
```bash
# 1. Upload mit und ohne Workshop-Consent
# 2. Social Media Checkboxen testen (Facebook, Instagram, TikTok)
# 3. Moderation-Filter prüfen:
# - Alle Gruppen
# - Nur Werkstatt
# - Facebook / Instagram / TikTok
# 4. Export-Funktion (CSV/JSON) testen
```
### Cleanup-System testen
```bash
# Test-Script verwenden:
./tests/test-cleanup.sh
# Oder manuell:
# 1. Upload ohne Approval
# 2. Gruppe zurückdatieren (Script verwendet)
# 3. Preview: GET http://localhost:5001/api/admin/cleanup/preview
# 4. Trigger: POST http://localhost:5001/api/admin/cleanup/trigger
# 5. Log prüfen: GET http://localhost:5001/api/admin/deletion-log
```
### API-Tests
```bash
# Consent-Endpoints:
curl http://localhost:5001/api/social-media/platforms
curl http://localhost:5001/api/groups/by-consent?workshopConsent=true
curl http://localhost:5001/api/admin/consents/export
# Upload testen (mit Consents):
curl -X POST http://localhost:5001/api/upload-batch \
-F "images=@test.jpg" \
-F "year=2025" \
-F "title=Test" \
-F "name=Developer" \
-F 'consents={"workshopConsent":true,"socialMediaConsents":[{"platformId":1,"consented":true}]}'
```
## Container-Management
```bash
# Status anzeigen:
docker compose -f docker/dev/docker-compose.yml ps
# Container neustarten:
docker compose -f docker/dev/docker-compose.yml restart
# Container neu bauen (nach Package-Updates):
docker compose -f docker/dev/docker-compose.yml build --no-cache
# Stoppen:
docker compose -f docker/dev/docker-compose.yml down
# Mit Volumes löschen (ACHTUNG: Löscht Datenbank!):
docker compose -f docker/dev/docker-compose.yml down -v
```
### Shell-Zugriff
```bash
# Frontend Container:
docker compose -f docker/dev/docker-compose.yml exec frontend-dev bash
# Backend Container:
docker compose -f docker/dev/docker-compose.yml exec backend-dev bash
# Datenbank-Shell:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db
```
## Debugging
### Backend Debugging
```bash
# Live-Logs:
docker compose -f docker/dev/docker-compose.yml logs -f backend-dev
# Nodemon Restart:
# → Änderungen in backend/src/** werden automatisch erkannt
# Fehlerhafte Migration fixen:
# 1. Migration-Eintrag löschen:
docker compose -f docker/dev/docker-compose.yml exec backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db "DELETE FROM schema_migrations WHERE migration_name='XXX.sql';"
# 2. Backend neustarten:
docker compose -f docker/dev/docker-compose.yml restart backend-dev
```
### Frontend Debugging
```bash
# React DevTools im Browser verwenden
# Network Tab für API-Calls prüfen
# Console für Fehler checken
# Nginx-Reload (bei Konfig-Änderungen):
docker compose -f docker/dev/docker-compose.yml exec frontend-dev nginx -s reload
```
### Datenbank-Backup & Restore
```bash
# Backup:
docker cp image-uploader-backend-dev:/usr/src/app/src/data/db/image_uploader.db ./backup.db
# Restore:
docker cp ./backup.db image-uploader-backend-dev:/usr/src/app/src/data/db/image_uploader.db
docker compose -f docker/dev/docker-compose.yml restart backend-dev
```
## Häufige Probleme
### "Migration failed" Fehler
**Problem**: Inline-Kommentare in SQL-Statements
**Lösung**: DatabaseManager entfernt diese automatisch (seit Commit 8e62475)
### "No such column: display_in_workshop"
**Problem**: Migration 005 nicht ausgeführt
**Lösung**: Backend neu starten oder manuell Migration ausführen
### Port 3000 bereits belegt
**Problem**: Anderer Prozess nutzt Port 3000
**Lösung**:
```bash
lsof -ti:3000 | xargs kill -9
# Oder Port in docker/dev/docker-compose.yml ändern
```
### Consent-Filter zeigt nichts
**Problem**: `display_in_workshop` fehlt in groupFormatter
**Lösung**: Bereits gefixt (Commit f049c47)
## Git Workflow
```bash
# Feature Branch erstellen:
git checkout -b feature/my-feature
# Änderungen committen:
git add .
git commit -m "feat: Add new feature"
# Vor Merge: Code testen!
# - Upload-Flow mit Consents
# - Moderation mit Filtern
# - Slideshow-Funktionalität
# - Cleanup-System
# Push:
git push origin feature/my-feature
```
### Git Hook (optional Absicherung)
Standard-Deployments sollten `ADMIN_SESSION_COOKIE_SECURE=true` behalten, damit das Session-Cookie nur über HTTPS übertragen wird.
Das bereitgestellte Pre-Commit-Hook stellt sicher, dass der Wert in `docker/prod/docker-compose.yml` automatisch auf `true` zurückgesetzt wird, falls er versehentlich verändert wurde (z.B. nach einem Test auf HTTP-only Hardware):
```bash
ln -s ../../scripts/git-hooks/pre-commit .git/hooks/pre-commit
```
Nach der Installation aktualisiert der Hook die Datei bei Bedarf und staged sie direkt.
Für lokale HTTP-Lab-Deployments nutze eine separate (gitignorierte) `docker-compose.override.yml`, um `ADMIN_SESSION_COOKIE_SECURE=false` nur zur Laufzeit zu setzen. Entfernen kannst du den Hook jederzeit über `rm .git/hooks/pre-commit`.
## Host-Separation Testing (Public/Internal Hosts)
Die Applikation unterstützt eine Public/Internal Host-Separation für die Produktion. Lokal kann dies mit /etc/hosts-Einträgen getestet werden.
### Schnellstart: Lokales Testing mit /etc/hosts
**1. Hosts-Datei bearbeiten:**
**Linux / Mac:**
```bash
sudo nano /etc/hosts
```
**Windows (als Administrator):**
1. Notepad öffnen (als Administrator)
2. Datei öffnen: `C:\Windows\System32\drivers\etc\hosts`
3. Dateifilter auf "Alle Dateien" ändern
Füge hinzu:
```
127.0.0.1 public.test.local
127.0.0.1 internal.test.local
```
**2. Docker .env anpassen:**
Bearbeite `docker/dev/frontend/config/.env`:
```bash
API_URL=http://localhost:5001
CLIENT_URL=http://localhost:3000
APP_VERSION=1.1.0
PUBLIC_HOST=public.test.local
INTERNAL_HOST=internal.test.local
```
Bearbeite `docker/dev/docker-compose.yml`:
```yaml
backend-dev:
environment:
- PUBLIC_HOST=public.test.local
- INTERNAL_HOST=internal.test.local
- ENABLE_HOST_RESTRICTION=true
- TRUST_PROXY_HOPS=0
frontend-dev:
environment:
- HOST=0.0.0.0
- DANGEROUSLY_DISABLE_HOST_CHECK=true
```
**3. Container starten:**
```bash
./dev.sh
```
**4. Im Browser testen:**
**Public Host** (`http://public.test.local:3000`):
- ✅ Upload-Seite funktioniert
- ✅ UUID Management funktioniert (`/manage/:token`)
- ✅ Social Media Badges angezeigt
- ❌ Kein Admin/Groups/Slideshow-Menü
-`/moderation` → 404
**Internal Host** (`http://internal.test.local:3000`):
- ✅ Alle Features verfügbar
- ✅ Admin-Bereich, Groups, Slideshow erreichbar
- ✅ Vollständiger API-Zugriff
### API-Tests mit curl
**Public Host - Blockierte Routen (403):**
```bash
curl -H "Host: public.test.local" http://localhost:5001/api/admin/deletion-log
curl -H "Host: public.test.local" http://localhost:5001/api/groups
curl -H "Host: public.test.local" http://localhost:5001/api/auth/login
```
**Public Host - Erlaubte Routen:**
```bash
curl -H "Host: public.test.local" http://localhost:5001/api/upload
curl -H "Host: public.test.local" http://localhost:5001/api/manage/YOUR-UUID
curl -H "Host: public.test.local" http://localhost:5001/api/social-media/platforms
```
**Internal Host - Alle Routen:**
```bash
curl -H "Host: internal.test.local" http://localhost:5001/api/groups
curl -H "Host: internal.test.local" http://localhost:5001/api/admin/deletion-log
```
### Frontend Code-Splitting testen
**Public Host:**
1. Browser DevTools → Network → JS Filter
2. Öffne `http://public.test.local:3000`
3. **Erwartung:** Slideshow/Admin/Groups-Bundles werden **nicht** geladen
4. Navigiere zu `/admin` → Redirect zu 404
**Internal Host:**
1. Öffne `http://internal.test.local:3000`
2. Navigiere zu `/slideshow`
3. **Erwartung:** Lazy-Bundle wird erst jetzt geladen (Code Splitting)
### Rate Limiting testen
Public Host: 20 Uploads/Stunde
```bash
for i in {1..25}; do
echo "Upload $i"
curl -X POST -H "Host: public.test.local" \
http://localhost:5001/api/upload \
-F "file=@test.jpg" -F "group=Test"
done
# Ab Upload 21: HTTP 429 (Too Many Requests)
```
### Troubleshooting
**"Invalid Host header"**
- Lösung: `DANGEROUSLY_DISABLE_HOST_CHECK=true` in `.env.development` (Frontend)
**"Alle Routen geben 403"**
- Prüfe `ENABLE_HOST_RESTRICTION=true`
- Prüfe `PUBLIC_HOST` / `INTERNAL_HOST` ENV-Variablen
- Container neu starten
**"public.test.local nicht erreichbar"**
- Prüfe `/etc/hosts`: `cat /etc/hosts | grep test.local`
- DNS-Cache leeren:
- **Linux:** `sudo systemd-resolve --flush-caches`
- **Mac:** `sudo dscacheutil -flushcache`
- **Windows:** `ipconfig /flushdns`
**Feature deaktivieren (Standard Dev):**
```yaml
backend-dev:
environment:
- ENABLE_HOST_RESTRICTION=false
```
### Production Setup
Für Production mit echten Subdomains siehe:
- `FeatureRequests/FEATURE_PLAN-FrontendPublic.md` (Sektion 12: Testing Strategy)
- nginx-proxy-manager Konfiguration erforderlich
- Hosts: `deinprojekt.hobbyhimmel.de` (public), `deinprojekt.lan.hobbyhimmel.de` (internal)
---
## Nützliche Befehle
```bash
# Alle Container-IDs:
docker ps -a
# Speicherplatz prüfen:
docker system df
# Ungenutztes aufräumen:
docker system prune -a
# Logs durchsuchen:
docker compose -f docker/dev/docker-compose.yml logs | grep ERROR
# Performance-Monitoring:
docker stats
```