Refactor: Dokumentation nach docs/ verschoben
- Alle Markdown-Dateien (außer README.md) nach docs/ verschoben - Referenzen in README.md aktualisiert - /docs zu .dockerignore hinzugefügt
This commit is contained in:
289
docs/CADDY_SETUP.md
Normal file
289
docs/CADDY_SETUP.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Caddy-Setup für Hördle
|
||||
|
||||
Diese Anleitung erklärt, wie du Caddy als Reverse-Proxy mit automatischen Let's Encrypt Wildcard-Zertifikaten für die Domains `hoerdle.de` und `hördle.de` (xn--hrdle-jua.de) einrichtest.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Caddy übernimmt folgende Aufgaben:
|
||||
- Automatische SSL/TLS-Zertifikate via Let's Encrypt
|
||||
- Wildcard-Zertifikate für beide Domains (inkl. Subdomains)
|
||||
- Reverse Proxy zu deinem Hördle-Container
|
||||
- HTTP zu HTTPS Redirect
|
||||
- Optimierte Einstellungen für Audio-Streaming und Uploads
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
1. Docker und Docker Compose installiert
|
||||
2. Zugriff auf deine GoDaddy Domain-Verwaltung
|
||||
3. Ports 80 und 443 müssen frei sein (Caddy übernimmt diese)
|
||||
|
||||
## Schritt 1: GoDaddy DNS-API-Zugangsdaten erstellen
|
||||
|
||||
Für Wildcard-Zertifikate benötigt Caddy DNS-01 Challenge, was API-Zugriff auf dein GoDaddy-Konto erfordert.
|
||||
|
||||
### GoDaddy API-Keys erstellen
|
||||
|
||||
1. Gehe zu [GoDaddy Developer Portal](https://developer.godaddy.com/)
|
||||
2. Melde dich mit deinem GoDaddy-Konto an
|
||||
3. Klicke auf **"Keys"** in der Navigation
|
||||
4. Klicke auf **"Create New API Key"**
|
||||
5. Fülle das Formular aus:
|
||||
- **Key Name**: z.B. "Hördle Caddy DNS"
|
||||
- **Environment**: Production (für echte Domains)
|
||||
6. Klicke auf **"Create"**
|
||||
7. **Wichtig**: Kopiere dir den **API Key** und das **API Secret** - das Secret wird nur einmal angezeigt!
|
||||
|
||||
### Alternative: Manuelle DNS-TXT-Records (ohne API)
|
||||
|
||||
Wenn du keine API-Keys verwenden möchtest, kannst du die DNS-TXT-Records manuell setzen. **Hinweis**: Dies ist nur für die initiale Zertifikatsanfrage möglich, nicht für automatische Erneuerungen.
|
||||
|
||||
Siehe Abschnitt "Manuelle DNS-Konfiguration (ohne API)" weiter unten.
|
||||
|
||||
## Schritt 2: Environment-Variablen konfigurieren
|
||||
|
||||
Erstelle eine `.env`-Datei im Projektverzeichnis (oder erweitere die bestehende):
|
||||
|
||||
```bash
|
||||
# GoDaddy API-Credentials für DNS-01 Challenge
|
||||
GODADDY_API_KEY=your_api_key_here
|
||||
GODADDY_API_SECRET=your_api_secret_here
|
||||
|
||||
# Optional: Email für Let's Encrypt Benachrichtigungen
|
||||
CADDY_EMAIL=markus@hoerdle.de
|
||||
```
|
||||
|
||||
**Wichtig**: Die `.env`-Datei sollte nicht in Git committed werden (sollte bereits in `.gitignore` sein).
|
||||
|
||||
## Schritt 3: Docker-Netzwerk erstellen
|
||||
|
||||
Caddy und Hördle müssen im gleichen Docker-Netzwerk kommunizieren:
|
||||
|
||||
```bash
|
||||
# Prüfe, ob das Netzwerk bereits existiert
|
||||
docker network ls | grep hoerdle
|
||||
|
||||
# Falls das Netzwerk bereits existiert, aber falsche Labels hat:
|
||||
# 1. Stoppe alle Container, die das Netzwerk nutzen
|
||||
docker compose -f docker-compose.yml down
|
||||
|
||||
# 2. Lösche das alte Netzwerk (falls keine Container mehr dranhängen)
|
||||
docker network rm hoerdle_default
|
||||
|
||||
# 3. Erstelle das Netzwerk neu
|
||||
docker network create hoerdle_default
|
||||
|
||||
# Falls das Netzwerk nicht existiert, erstelle es:
|
||||
docker network create hoerdle_default
|
||||
```
|
||||
|
||||
**Hinweis**: Die docker-compose.caddy.yml ist so konfiguriert, dass sie das Netzwerk als externes Netzwerk nutzt. Das bedeutet, dass das Netzwerk bereits existieren muss, bevor Caddy gestartet wird.
|
||||
|
||||
## Schritt 4: Caddy starten
|
||||
|
||||
### Option A: Mit docker-compose (Empfohlen)
|
||||
|
||||
```bash
|
||||
# Starte Hördle + Caddy zusammen
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
|
||||
|
||||
# Nur Caddy starten (wenn Hördle bereits läuft)
|
||||
docker compose -f docker-compose.caddy.yml --profile production up -d
|
||||
```
|
||||
|
||||
### Option B: Nur Caddy starten (Hördle läuft bereits)
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.caddy.yml --profile production up -d
|
||||
```
|
||||
|
||||
## Schritt 5: DNS-Konfiguration in GoDaddy
|
||||
|
||||
### Automatisch (mit API-Keys)
|
||||
|
||||
Wenn du API-Keys konfiguriert hast, wird Caddy automatisch die benötigten DNS-TXT-Records erstellen. Keine manuellen DNS-Änderungen nötig!
|
||||
|
||||
### Manuell (ohne API-Keys)
|
||||
|
||||
Wenn du die API-Keys nicht verwenden möchtest, musst du die DNS-TXT-Records manuell setzen:
|
||||
|
||||
#### Für hoerdle.de:
|
||||
|
||||
1. Gehe zu deinem [GoDaddy DNS-Verwaltung](https://dcc.godaddy.com/manage/YOUR_DOMAIN/dns)
|
||||
2. Für jedes Wildcard-Zertifikat benötigst du einen TXT-Record:
|
||||
- **Typ**: TXT
|
||||
- **Name**: `_acme-challenge`
|
||||
- **Wert**: (wird von Let's Encrypt generiert - siehe Caddy-Logs)
|
||||
- **TTL**: 600 (10 Minuten)
|
||||
|
||||
**Wichtig**: Für Wildcard-Zertifikate brauchst du:
|
||||
- Einen TXT-Record für `_acme-challenge.hoerdle.de` (Domain selbst)
|
||||
- Einen TXT-Record für `_acme-challenge.*.hoerdle.de` (Wildcard)
|
||||
|
||||
#### Für hördle.de (xn--hrdle-jua.de):
|
||||
|
||||
Das gleiche Vorgehen für die Punycode-Domain:
|
||||
- `_acme-challenge.xn--hrdle-jua.de`
|
||||
- `_acme-challenge.*.xn--hrdle-jua.de`
|
||||
|
||||
**Hinweis**: Die manuelle Methode funktioniert nur für die initiale Zertifikatsanfrage. Für automatische Erneuerungen benötigst du die API-Keys.
|
||||
|
||||
## Schritt 6: Prüfen, ob alles funktioniert
|
||||
|
||||
### Caddy-Logs ansehen
|
||||
|
||||
```bash
|
||||
docker logs -f hoerdle-caddy
|
||||
```
|
||||
|
||||
Du solltest sehen:
|
||||
- Caddy startet erfolgreich
|
||||
- Let's Encrypt-Zertifikate werden angefordert
|
||||
- Zertifikate sind gültig
|
||||
|
||||
### Zertifikate prüfen
|
||||
|
||||
```bash
|
||||
# Prüfe Zertifikate im Browser
|
||||
# Öffne: https://hoerdle.de
|
||||
# Öffne: https://hördle.de
|
||||
```
|
||||
|
||||
Oder via Command-Line:
|
||||
|
||||
```bash
|
||||
# Prüfe Zertifikat für hoerdle.de
|
||||
openssl s_client -connect hoerdle.de:443 -servername hoerdle.de < /dev/null 2>/dev/null | openssl x509 -noout -text | grep "Subject:"
|
||||
|
||||
# Prüfe Zertifikat für hördle.de
|
||||
openssl s_client -connect xn--hrdle-jua.de:443 -servername xn--hrdle-jua.de < /dev/null 2>/dev/null | openssl x509 -noout -text | grep "Subject:"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Caddy startet nicht
|
||||
|
||||
**Problem**: Container stoppt sofort nach Start.
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy`
|
||||
2. Prüfe Caddyfile-Syntax: `docker run --rm -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile`
|
||||
3. Prüfe, ob Ports 80/443 frei sind: `sudo netstat -tlnp | grep -E ':80|:443'`
|
||||
|
||||
### Zertifikate werden nicht erstellt
|
||||
|
||||
**Problem**: Let's Encrypt-Zertifikate werden nicht angefordert.
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe GoDaddy API-Credentials in `.env`
|
||||
2. Prüfe Caddy-Logs für DNS-Challenge-Fehler
|
||||
3. Stelle sicher, dass die Domains korrekt auf deinen Server zeigen (A-Records)
|
||||
4. Bei manueller DNS-Konfiguration: Prüfe, ob TXT-Records korrekt gesetzt sind
|
||||
|
||||
### DNS-Challenge schlägt fehl
|
||||
|
||||
**Problem**: DNS-01 Challenge kann DNS-Records nicht erstellen.
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe GoDaddy API-Permissions
|
||||
2. Stelle sicher, dass API-Keys Production-Keys sind (nicht Development)
|
||||
3. Prüfe Domain-Ownership in GoDaddy
|
||||
4. Warte einige Minuten - DNS-Propagierung kann dauern
|
||||
|
||||
### Audio-Dateien funktionieren nicht
|
||||
|
||||
**Problem**: MP3-Dateien werden nicht korrekt gestreamt.
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy | grep -i range`
|
||||
2. Prüfe, ob Range-Header weitergegeben werden (Browser DevTools → Network)
|
||||
3. Stelle sicher, dass der `/uploads/` Handle korrekt konfiguriert ist
|
||||
|
||||
### Container können nicht kommunizieren
|
||||
|
||||
**Problem**: Caddy kann den hoerdle-Container nicht erreichen.
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe, ob beide Container im gleichen Netzwerk sind:
|
||||
```bash
|
||||
docker network inspect hoerdle_default
|
||||
```
|
||||
2. Prüfe, ob hoerdle-Container läuft: `docker ps | grep hoerdle`
|
||||
3. Teste Verbindung von Caddy zu Hördle:
|
||||
```bash
|
||||
docker exec hoerdle-caddy wget -O- http://hoerdle:3000/api/health
|
||||
```
|
||||
**Hinweis**: Der Container-Port ist 3000 (nicht 3010, das ist nur der Host-Port).
|
||||
|
||||
### Netzwerk-Warnung beim Deployment
|
||||
|
||||
**Problem**: Warnung `network hoerdle_default was found but has incorrect label`
|
||||
|
||||
**Erklärung**: Diese Warnung ist **harmlos** und kann ignoriert werden. Docker Compose funktioniert trotzdem einwandfrei. Sie entsteht, wenn das Netzwerk bereits existiert, aber nicht von Docker Compose erstellt wurde.
|
||||
|
||||
**Optional: Warnung beheben** (nur wenn sie stört):
|
||||
```bash
|
||||
# Reparatur-Skript ausführen (stoppt Container kurz)
|
||||
./scripts/fix-network.sh
|
||||
|
||||
# Danach Container neu starten
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**Hinweis**: Das Reparatur-Skript stoppt alle Container kurz, die das Netzwerk nutzen. In Produktion sollte dies außerhalb der Hauptnutzungszeit erfolgen.
|
||||
|
||||
## Deployment-Workflow
|
||||
|
||||
### Caddy nur in Produktion aktivieren
|
||||
|
||||
Die `docker-compose.caddy.yml` verwendet das `production`-Profile. Um Caddy zu aktivieren:
|
||||
|
||||
```bash
|
||||
# Mit Production-Profile
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
|
||||
|
||||
# Ohne Caddy (nur Hördle)
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### Caddy aktualisieren
|
||||
|
||||
```bash
|
||||
# Pull neues Caddy-Image
|
||||
docker compose -f docker-compose.caddy.yml pull
|
||||
|
||||
# Restart Caddy-Container
|
||||
docker compose -f docker-compose.caddy.yml --profile production restart caddy
|
||||
```
|
||||
|
||||
### Caddy-Konfiguration ändern
|
||||
|
||||
Nach Änderungen am Caddyfile:
|
||||
|
||||
```bash
|
||||
# Caddyfile validieren
|
||||
docker run --rm -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile
|
||||
|
||||
# Caddy neu laden (ohne Downtime)
|
||||
docker compose -f docker-compose.caddy.yml --profile production exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### API-Keys schützen
|
||||
|
||||
- **Niemals** API-Keys in Git committen
|
||||
- Verwende `.env`-Dateien (sollten in `.gitignore` sein)
|
||||
- Setze minimale Berechtigungen für API-Keys in GoDaddy
|
||||
- Rotiere API-Keys regelmäßig
|
||||
|
||||
### Firewall
|
||||
|
||||
Stelle sicher, dass nur Ports 80 und 443 öffentlich erreichbar sind. Port 3010 (Hördle) sollte nicht öffentlich erreichbar sein.
|
||||
|
||||
## Weitere Ressourcen
|
||||
|
||||
- [Caddy Dokumentation](https://caddyserver.com/docs/)
|
||||
- [Caddy DNS-Provider](https://caddyserver.com/docs/modules/tls.dns)
|
||||
- [GoDaddy API Dokumentation](https://developer.godaddy.com/doc/endpoint/domains)
|
||||
- [Let's Encrypt Wildcard-Zertifikate](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
|
||||
|
||||
183
docs/CADDY_TROUBLESHOOTING.md
Normal file
183
docs/CADDY_TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Caddy Zertifikat-Troubleshooting
|
||||
|
||||
## Problem: Zertifikat für Punycode-Domain (hördle.de / xn--hrdle-jua.de) fehlt
|
||||
|
||||
Wenn die Domain `hördle.de` (xn--hrdle-jua.de) einen `ERR_SSL_PROTOCOL_ERROR` zeigt, bedeutet das, dass kein gültiges SSL-Zertifikat vorhanden ist.
|
||||
|
||||
### Schritt 1: Zertifikat-Status prüfen
|
||||
|
||||
Führe das Check-Script aus:
|
||||
|
||||
```bash
|
||||
./scripts/check-caddy-certificates.sh
|
||||
```
|
||||
|
||||
Dieses Script prüft:
|
||||
- Ob Caddy läuft
|
||||
- Welche Zertifikate vorhanden sind
|
||||
- Ob die DNS-Einträge korrekt sind
|
||||
- Ob die HTTPS-Verbindungen funktionieren
|
||||
|
||||
### Schritt 2: DNS-Einträge prüfen
|
||||
|
||||
**Wichtig**: Beide Domains müssen auf die gleiche Server-IP zeigen!
|
||||
|
||||
#### In GoDaddy prüfen:
|
||||
|
||||
1. Gehe zu [GoDaddy DNS-Verwaltung](https://dcc.godaddy.com/manage/hoerdle.de/dns)
|
||||
2. Prüfe die A-Records:
|
||||
|
||||
**Für hoerdle.de:**
|
||||
- Name: `@` oder `hoerdle.de`
|
||||
- Typ: `A`
|
||||
- Wert: `DEINE_SERVER_IP`
|
||||
|
||||
**Für hördle.de (Punycode):**
|
||||
- Name: `@` oder `xn--hrdle-jua.de` (oder der Unicode-Name, falls unterstützt)
|
||||
- Typ: `A`
|
||||
- Wert: **GLEICHE_SERVER_IP wie hoerdle.de**
|
||||
|
||||
#### DNS manuell testen:
|
||||
|
||||
```bash
|
||||
# Prüfe hoerdle.de
|
||||
dig +short hoerdle.de @8.8.8.8
|
||||
|
||||
# Prüfe xn--hrdle-jua.de (Punycode)
|
||||
dig +short xn--hrdle-jua.de @8.8.8.8
|
||||
|
||||
# Beide sollten die gleiche IP zurückgeben!
|
||||
```
|
||||
|
||||
### Schritt 3: Zertifikat neu erstellen
|
||||
|
||||
Wenn die DNS-Einträge korrekt sind, lösche das alte (fehlgeschlagene) Zertifikat und lass Caddy es neu erstellen:
|
||||
|
||||
```bash
|
||||
./scripts/renew-caddy-certificates.sh
|
||||
```
|
||||
|
||||
Wähle Option 2: "Nur Zertifikat für xn--hrdle-jua.de löschen"
|
||||
|
||||
### Schritt 4: Caddy-Logs überwachen
|
||||
|
||||
Während Caddy das Zertifikat erstellt, überwache die Logs:
|
||||
|
||||
```bash
|
||||
docker logs hoerdle-caddy -f
|
||||
```
|
||||
|
||||
Du solltest sehen:
|
||||
- `[INFO] attempting ACME challenge` - Caddy versucht die Challenge
|
||||
- `[INFO] successfully completed ACME challenge` - Challenge erfolgreich
|
||||
- `[INFO] certificate obtained successfully` - Zertifikat erstellt
|
||||
|
||||
Bei Fehlern siehst du:
|
||||
- `[ERROR] acme: error` - Challenge fehlgeschlagen
|
||||
- `[ERROR] unable to validate` - Validierung fehlgeschlagen
|
||||
|
||||
### Schritt 5: Häufige Probleme und Lösungen
|
||||
|
||||
#### Problem 1: DNS zeigt auf falsche IP
|
||||
|
||||
**Symptom**: `dig` zeigt eine andere IP als erwartet
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe DNS-Einträge in GoDaddy
|
||||
2. Warte auf DNS-Propagierung (kann 5-60 Minuten dauern)
|
||||
3. Verwende einen DNS-Checker: https://www.whatsmydns.net/
|
||||
|
||||
#### Problem 2: Port 80 nicht erreichbar
|
||||
|
||||
**Symptom**: Caddy-Logs zeigen "connection refused" oder Timeout
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe Firewall: `sudo ufw status`
|
||||
2. Prüfe ob Port 80 offen ist: `sudo netstat -tulpn | grep :80`
|
||||
3. Prüfe ob Caddy auf Port 80 lauscht: `docker exec hoerdle-caddy netstat -tulpn | grep :80`
|
||||
|
||||
#### Problem 3: Let's Encrypt Rate Limit
|
||||
|
||||
**Symptom**: Logs zeigen "too many certificates already issued"
|
||||
|
||||
**Lösung**:
|
||||
- Warte 1 Woche (Rate Limit von Let's Encrypt)
|
||||
- Oder verwende Staging-Environment zum Testen:
|
||||
```caddyfile
|
||||
tls {
|
||||
staging
|
||||
}
|
||||
```
|
||||
|
||||
#### Problem 4: Punycode-Domain wird nicht erkannt
|
||||
|
||||
**Symptom**: Caddy erstellt Zertifikat nur für hoerdle.de, nicht für xn--hrdle-jua.de
|
||||
|
||||
**Lösung**:
|
||||
1. Prüfe ob beide Domains in der Caddyfile stehen
|
||||
2. Prüfe DNS-Einträge (siehe Schritt 2)
|
||||
3. Erzwinge Zertifikat-Erstellung (siehe Schritt 3)
|
||||
|
||||
### Manuelle Zertifikat-Löschung
|
||||
|
||||
Falls das Script nicht funktioniert, kannst du Zertifikate manuell löschen:
|
||||
|
||||
```bash
|
||||
# Alle Zertifikate löschen
|
||||
docker exec hoerdle-caddy rm -rf /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/*
|
||||
|
||||
# Nur Punycode-Zertifikat löschen (manuell)
|
||||
docker exec hoerdle-caddy find /data/caddy/certificates -name "*xn--*" -delete
|
||||
|
||||
# Container neu starten
|
||||
docker compose -f docker-compose.caddy.yml --profile production restart caddy
|
||||
```
|
||||
|
||||
### DNS-Propagierung prüfen
|
||||
|
||||
Nach DNS-Änderungen kann es bis zu 60 Minuten dauern, bis alle DNS-Server aktualisiert sind:
|
||||
|
||||
```bash
|
||||
# Prüfe DNS-Propagierung weltweit
|
||||
curl "https://dnschecker.org/#A/hoerdle.de"
|
||||
curl "https://dnschecker.org/#A/xn--hrdle-jua.de"
|
||||
```
|
||||
|
||||
### Test-Zertifikat erstellen (Staging)
|
||||
|
||||
Zum Testen ohne Rate-Limits kannst du ein Staging-Zertifikat erstellen:
|
||||
|
||||
1. Temporär Caddyfile ändern (in beiden Domain-Blocks):
|
||||
```caddyfile
|
||||
tls {
|
||||
staging
|
||||
}
|
||||
```
|
||||
|
||||
2. Container neu starten
|
||||
3. Zertifikat erstellen lassen
|
||||
4. Zurück zu Produktion ändern (Staging-Block entfernen)
|
||||
5. Erneut Container neu starten
|
||||
|
||||
### Verifizieren, dass es funktioniert
|
||||
|
||||
Nach erfolgreicher Zertifikats-Erstellung:
|
||||
|
||||
```bash
|
||||
# Teste HTTPS-Verbindung
|
||||
curl -I https://hoerdle.de
|
||||
curl -I https://xn--hrdle-jua.de
|
||||
|
||||
# Prüfe Zertifikat-Details
|
||||
echo | openssl s_client -connect hoerdle.de:443 -servername hoerdle.de 2>/dev/null | openssl x509 -noout -subject -dates
|
||||
echo | openssl s_client -connect xn--hrdle-jua.de:443 -servername xn--hrdle-jua.de 2>/dev/null | openssl x509 -noout -subject -dates
|
||||
```
|
||||
|
||||
### Support
|
||||
|
||||
Falls das Problem weiterhin besteht:
|
||||
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy`
|
||||
2. Prüfe DNS: `dig +short xn--hrdle-jua.de @8.8.8.8`
|
||||
3. Prüfe Firewall: `sudo ufw status`
|
||||
4. Prüfe Port-Zugriff: `curl -I http://hoerdle.de`
|
||||
|
||||
185
docs/DEBUG_VERSION.md
Normal file
185
docs/DEBUG_VERSION.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Debug Version Display - Remote Server Checklist
|
||||
|
||||
## 1. Überprüfe Git-Tags auf dem Remote-Server
|
||||
|
||||
```bash
|
||||
# Im Projekt-Verzeichnis auf dem Remote-Server
|
||||
cd /path/to/hoerdle
|
||||
|
||||
# Zeige alle Tags
|
||||
git tag -l
|
||||
|
||||
# Zeige aktuellen Tag/Version
|
||||
git describe --tags --always
|
||||
|
||||
# Wenn keine Tags angezeigt werden:
|
||||
git fetch --tags
|
||||
git describe --tags --always
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** Sollte `v0.1.0.2` oder ähnlich zeigen
|
||||
|
||||
---
|
||||
|
||||
## 2. Überprüfe die version.txt im Container
|
||||
|
||||
```bash
|
||||
# Zeige den Inhalt der Version-Datei im laufenden Container
|
||||
docker exec hoerdle cat /app/version.txt
|
||||
|
||||
# Sollte die Version zeigen, z.B. "v0.1.0.2"
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** Die aktuelle Version, nicht "unknown" oder "dev"
|
||||
|
||||
---
|
||||
|
||||
## 3. Überprüfe die Umgebungsvariable im Container
|
||||
|
||||
```bash
|
||||
# Zeige alle Umgebungsvariablen
|
||||
docker exec hoerdle env | grep APP_VERSION
|
||||
|
||||
# Sollte APP_VERSION=v0.1.0.2 oder ähnlich zeigen
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** `APP_VERSION=v0.1.0.2`
|
||||
|
||||
---
|
||||
|
||||
## 4. Überprüfe die Container-Logs beim Start
|
||||
|
||||
```bash
|
||||
# Zeige die letzten Logs beim Container-Start
|
||||
docker logs hoerdle | head -20
|
||||
|
||||
# Suche speziell nach Version-Ausgaben
|
||||
docker logs hoerdle | grep -i version
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** Eine Zeile wie "App version: v0.1.0.2"
|
||||
|
||||
---
|
||||
|
||||
## 5. Teste die API direkt
|
||||
|
||||
```bash
|
||||
# Rufe die Version-API auf
|
||||
curl http://localhost:3010/api/version
|
||||
|
||||
# Sollte JSON zurückgeben: {"version":"v0.1.0.2"}
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** `{"version":"v0.1.0.2"}`
|
||||
|
||||
---
|
||||
|
||||
## 6. Überprüfe wann der Container gebaut wurde
|
||||
|
||||
```bash
|
||||
# Zeige Image-Informationen
|
||||
docker images | grep hoerdle
|
||||
|
||||
# Zeige detaillierte Container-Informationen
|
||||
docker inspect hoerdle | grep -i created
|
||||
```
|
||||
|
||||
**Wichtig:** Wenn das Image vor deinem letzten Deployment erstellt wurde, wurde es noch nicht neu gebaut!
|
||||
|
||||
---
|
||||
|
||||
## 7. Überprüfe Build-Logs
|
||||
|
||||
```bash
|
||||
# Baue das Image neu und beobachte die Ausgabe
|
||||
docker compose build --no-cache 2>&1 | tee build.log
|
||||
|
||||
# Suche nach der Version-Ausgabe im Build
|
||||
grep -i "Building version" build.log
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:** Eine Zeile wie "Building version: v0.1.0.2"
|
||||
|
||||
---
|
||||
|
||||
## Häufige Probleme und Lösungen
|
||||
|
||||
### Problem 1: Tags nicht auf dem Server
|
||||
```bash
|
||||
git fetch --tags
|
||||
git describe --tags --always
|
||||
```
|
||||
|
||||
### Problem 2: Container wurde nicht neu gebaut
|
||||
```bash
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Problem 3: Alte version.txt im Container
|
||||
```bash
|
||||
# Stoppe Container, lösche Image, baue neu
|
||||
docker compose down
|
||||
docker rmi $(docker images | grep hoerdle | awk '{print $3}')
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Problem 4: .git Verzeichnis nicht im Build-Context
|
||||
```bash
|
||||
# Überprüfe ob .git existiert
|
||||
ls -la .git
|
||||
|
||||
# Überprüfe .dockerignore (sollte .git NICHT ausschließen)
|
||||
cat .dockerignore 2>/dev/null || echo "Keine .dockerignore Datei"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vollständiger Neustart (wenn nichts anderes hilft)
|
||||
|
||||
```bash
|
||||
# 1. Stoppe alles
|
||||
docker compose down
|
||||
|
||||
# 2. Lösche alte Images
|
||||
docker rmi $(docker images | grep hoerdle | awk '{print $3}')
|
||||
|
||||
# 3. Hole neueste Änderungen und Tags
|
||||
git pull
|
||||
git fetch --tags
|
||||
|
||||
# 4. Überprüfe Version lokal
|
||||
git describe --tags --always
|
||||
|
||||
# 5. Baue komplett neu
|
||||
docker compose build --no-cache
|
||||
|
||||
# 6. Starte Container
|
||||
docker compose up -d
|
||||
|
||||
# 7. Überprüfe Logs
|
||||
docker logs hoerdle | grep -i version
|
||||
|
||||
# 8. Teste API
|
||||
curl http://localhost:3010/api/version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging-Befehl für alle Checks auf einmal
|
||||
|
||||
```bash
|
||||
echo "=== Git Tags ===" && \
|
||||
git describe --tags --always && \
|
||||
echo -e "\n=== version.txt im Container ===" && \
|
||||
docker exec hoerdle cat /app/version.txt 2>/dev/null || echo "Container läuft nicht oder Datei fehlt" && \
|
||||
echo -e "\n=== APP_VERSION Env ===" && \
|
||||
docker exec hoerdle env | grep APP_VERSION || echo "Variable nicht gesetzt" && \
|
||||
echo -e "\n=== API Response ===" && \
|
||||
curl -s http://localhost:3010/api/version && \
|
||||
echo -e "\n\n=== Container Created ===" && \
|
||||
docker inspect hoerdle | grep -i created | head -1
|
||||
```
|
||||
|
||||
Kopiere diesen Befehl und führe ihn auf dem Remote-Server aus. Schicke mir die Ausgabe!
|
||||
116
docs/DEPLOYMENT.md
Normal file
116
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Deployment Guide
|
||||
|
||||
## Automated Deployment
|
||||
|
||||
Use the deployment script for zero-downtime deployments:
|
||||
|
||||
```bash
|
||||
./scripts/deploy.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Create a database backup
|
||||
2. Pull latest changes from git
|
||||
3. Fetch all git tags (for version display)
|
||||
4. Build the new Docker image
|
||||
5. Restart the container with minimal downtime
|
||||
6. Clean up old images
|
||||
|
||||
## Manual Deployment
|
||||
|
||||
If you need to deploy manually:
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull
|
||||
|
||||
# Fetch tags (important for version display!)
|
||||
git fetch --tags
|
||||
|
||||
# Build and restart
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Version Display
|
||||
|
||||
The app displays the current version in the footer. The version is determined as follows:
|
||||
|
||||
1. **During Docker build**: The version is extracted from git tags using `git describe --tags --always`
|
||||
2. **At runtime**: The version is read from `/app/version.txt` and exposed via the `/api/version` endpoint
|
||||
3. **Local development**: The version is extracted directly from git on each request
|
||||
|
||||
### Building with a specific version
|
||||
|
||||
You can override the version during build:
|
||||
|
||||
```bash
|
||||
docker compose build --build-arg APP_VERSION=v1.2.3
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If the version shows as "dev" or "unknown":
|
||||
|
||||
1. Make sure git tags are pushed to the remote repository:
|
||||
```bash
|
||||
git push --tags
|
||||
```
|
||||
|
||||
2. On the deployment server, fetch the tags:
|
||||
```bash
|
||||
git fetch --tags
|
||||
```
|
||||
|
||||
3. Verify tags are available:
|
||||
```bash
|
||||
git describe --tags --always
|
||||
```
|
||||
|
||||
4. Rebuild the Docker image:
|
||||
```bash
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
The container includes a health check that monitors the `/api/daily` endpoint. Check the health status:
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
Look for the "healthy" status in the STATUS column.
|
||||
|
||||
## Caddy Reverse Proxy (Optional - Production)
|
||||
|
||||
For production deployments with automatic SSL/TLS certificates, Caddy can be used as a reverse proxy. Caddy provides:
|
||||
|
||||
- Automatic Let's Encrypt certificates (including wildcard certificates)
|
||||
- HTTP to HTTPS redirect
|
||||
- Optimized settings for audio streaming and file uploads
|
||||
- Support for both `hoerdle.de` and `hördle.de` (Punycode: `xn--hrdle-jua.de`)
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Follow the setup guide**: See `CADDY_SETUP.md` for detailed instructions
|
||||
2. **Configure environment variables**: Add GoDaddy API credentials to your `.env` file
|
||||
3. **Start with Caddy**:
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
|
||||
```
|
||||
|
||||
### Without Caddy
|
||||
|
||||
If you don't want to use Caddy, you can deploy normally:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
The application will still be accessible on port 3010, but you'll need to configure SSL/TLS separately (e.g., with nginx).
|
||||
|
||||
### Caddy Troubleshooting
|
||||
|
||||
See `CADDY_SETUP.md` for detailed troubleshooting information.
|
||||
106
docs/DOCKER_BUILD_FIX.md
Normal file
106
docs/DOCKER_BUILD_FIX.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Docker Build Fix: Upload-Dateien ausschließen
|
||||
|
||||
## Problem
|
||||
|
||||
Der Docker Build schlug fehl mit:
|
||||
```
|
||||
no space left on device
|
||||
```
|
||||
|
||||
**Ursache**: Die großen MP3-Dateien in `public/uploads/` wurden in den Build-Context kopiert und verbrauchten zu viel Speicherplatz.
|
||||
|
||||
## Lösung
|
||||
|
||||
Eine `.dockerignore` Datei wurde erstellt, die folgende Dateien/Ordner vom Build ausschließt:
|
||||
|
||||
- `public/uploads/*` - Upload-Dateien (werden als Volume gemountet)
|
||||
- `data/*` - Datenbank-Dateien (werden als Volume gemountet)
|
||||
- `node_modules` - werden während des Builds installiert
|
||||
- `.next`, `out`, `build` - Build-Artefakte
|
||||
- Backup-Dateien, Logs, temporäre Dateien
|
||||
|
||||
## Zusätzliche Maßnahmen
|
||||
|
||||
Falls der Build weiterhin Probleme macht:
|
||||
|
||||
### 1. Docker aufräumen
|
||||
|
||||
```bash
|
||||
# Entferne nicht verwendete Images
|
||||
docker image prune -a
|
||||
|
||||
# Entferne nicht verwendete Container
|
||||
docker container prune
|
||||
|
||||
# Entferne nicht verwendete Volumes (VORSICHT: kann Daten löschen!)
|
||||
docker volume prune
|
||||
|
||||
# Kompletter Cleanup (alles außer laufenden Containern)
|
||||
docker system prune -a
|
||||
```
|
||||
|
||||
### 2. Speicherplatz prüfen
|
||||
|
||||
```bash
|
||||
# Zeige Speicherplatz
|
||||
df -h
|
||||
|
||||
# Zeige Docker-Speicherverbrauch
|
||||
docker system df
|
||||
|
||||
# Zeige größte Images
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | sort -k3 -h
|
||||
```
|
||||
|
||||
### 3. Build-Kontext prüfen
|
||||
|
||||
```bash
|
||||
# Prüfe was in den Build-Context kopiert wird
|
||||
docker build --no-cache --progress=plain -t test-build . 2>&1 | grep "transferring context"
|
||||
```
|
||||
|
||||
### 4. Upload-Dateien manuell ausschließen
|
||||
|
||||
Falls die `.dockerignore` nicht greift, können Upload-Dateien vorübergehend verschoben werden:
|
||||
|
||||
```bash
|
||||
# Vor dem Build
|
||||
mv public/uploads public/uploads.backup
|
||||
mkdir -p public/uploads
|
||||
touch public/uploads/.gitkeep
|
||||
|
||||
# Build durchführen
|
||||
docker compose build
|
||||
|
||||
# Uploads wiederherstellen
|
||||
rm -rf public/uploads
|
||||
mv public/uploads.backup public/uploads
|
||||
```
|
||||
|
||||
## Wichtig
|
||||
|
||||
Die Upload-Dateien werden **nicht** ins Docker-Image kopiert, sondern als Volume gemountet:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./public/uploads:/app/public/uploads
|
||||
```
|
||||
|
||||
Das bedeutet:
|
||||
- Upload-Dateien bleiben auf dem Host-System
|
||||
- Sie werden zur Laufzeit gemountet
|
||||
- Sie sollten **nicht** ins Image kopiert werden (spart viel Speicher)
|
||||
|
||||
## Verifikation
|
||||
|
||||
Nach dem Build sollte das Image deutlich kleiner sein:
|
||||
|
||||
```bash
|
||||
# Zeige Image-Größe
|
||||
docker images hoerdle-hoerdle
|
||||
|
||||
# Prüfe ob Uploads im Image sind
|
||||
docker run --rm hoerdle-hoerdle ls -lh /app/public/uploads
|
||||
# Sollte nur .gitkeep oder Covers zeigen, keine MP3-Dateien
|
||||
```
|
||||
|
||||
83
docs/FIX_I18N.md
Normal file
83
docs/FIX_I18N.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Fix für i18n-Daten (String → JSON Konvertierung)
|
||||
|
||||
## Problem
|
||||
Die Datenbank hat Genre-/Special-/News-Namen als einfache Strings (`"Rock"`) statt JSON (`{"de": "Rock", "en": "Rock"}`) gespeichert, was zu `SyntaxError: "Rock" is not valid JSON` führt.
|
||||
|
||||
## Lösung: Manuell ausführen
|
||||
|
||||
Führe diese Befehle **direkt auf dem Server** aus:
|
||||
|
||||
```bash
|
||||
cd ~/hoerdle
|
||||
|
||||
# 1. Backup erstellen
|
||||
docker cp hoerdle:/app/data/prod.db ./data/prod.db.backup.$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# 2. Kopiere DB lokal
|
||||
docker cp hoerdle:/app/data/prod.db ./data/prod.db.tmp
|
||||
|
||||
# 3. Setze Berechtigungen
|
||||
sudo chmod 666 ./data/prod.db.tmp
|
||||
sudo chmod 775 ./data
|
||||
|
||||
# 4. Prüfe ob sqlite3 installiert ist
|
||||
which sqlite3 || sudo apt-get install -y sqlite3
|
||||
|
||||
# 5. Fixe die Datenbank (kopiere diesen Block komplett)
|
||||
sqlite3 ./data/prod.db.tmp << 'EOF'
|
||||
UPDATE Genre SET name = json_object('de', name, 'en', name) WHERE typeof(name) = 'text' AND name NOT LIKE '{%';
|
||||
UPDATE Genre SET subtitle = json_object('de', subtitle, 'en', subtitle) WHERE subtitle IS NOT NULL AND typeof(subtitle) = 'text' AND subtitle NOT LIKE '{%';
|
||||
UPDATE Special SET name = json_object('de', name, 'en', name) WHERE typeof(name) = 'text' AND name NOT LIKE '{%';
|
||||
UPDATE Special SET subtitle = json_object('de', subtitle, 'en', subtitle) WHERE subtitle IS NOT NULL AND typeof(subtitle) = 'text' AND subtitle NOT LIKE '{%';
|
||||
UPDATE News SET title = json_object('de', title, 'en', title) WHERE typeof(title) = 'text' AND title NOT LIKE '{%';
|
||||
UPDATE News SET content = json_object('de', content, 'en', content) WHERE typeof(content) = 'text' AND content NOT LIKE '{%';
|
||||
SELECT '✅ Fertig!' as status;
|
||||
EOF
|
||||
|
||||
# 6. Kopiere zurück
|
||||
docker cp ./data/prod.db.tmp hoerdle:/app/data/prod.db
|
||||
|
||||
# 7. Aufräumen
|
||||
rm ./data/prod.db.tmp
|
||||
|
||||
# 8. Container neu starten
|
||||
docker compose restart hoerdle
|
||||
|
||||
# 9. Logs prüfen
|
||||
docker logs hoerdle --tail=50
|
||||
```
|
||||
|
||||
Falls Schritt 5 mit "permission denied" fehlschlägt, verwende `sudo`:
|
||||
|
||||
```bash
|
||||
sudo sqlite3 ./data/prod.db.tmp << 'EOF'
|
||||
[... SQL-Befehle wie oben ...]
|
||||
EOF
|
||||
```
|
||||
|
||||
## Automatisiertes Skript
|
||||
|
||||
Alternativ kannst du das automatische Skript verwenden:
|
||||
|
||||
```bash
|
||||
./scripts/fix-i18n-easy.sh
|
||||
```
|
||||
|
||||
Oder das lokale Skript:
|
||||
|
||||
```bash
|
||||
./scripts/fix-i18n-local.sh
|
||||
```
|
||||
|
||||
## Prüfen ob es funktioniert hat
|
||||
|
||||
Nach dem Neustart sollte die Seite wieder funktionieren:
|
||||
|
||||
```bash
|
||||
# Prüfe Logs (sollte keine JSON-Fehler mehr zeigen)
|
||||
docker logs hoerdle --tail=100 | grep -i "json\|error" || echo "✅ Keine JSON-Fehler gefunden"
|
||||
|
||||
# Teste die Seite
|
||||
curl -s https://hoerdle.de/de | head -20
|
||||
```
|
||||
|
||||
349
docs/I18N.md
Normal file
349
docs/I18N.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# Internationalisierung (i18n) Dokumentation
|
||||
|
||||
Hördle unterstützt vollständige Mehrsprachigkeit (Internationalisierung) für Deutsch und Englisch.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die i18n-Implementierung basiert auf [next-intl](https://next-intl-docs.vercel.app/) und nutzt den Next.js App Router mit dynamischen `[locale]`-Segmenten.
|
||||
|
||||
## Unterstützte Sprachen
|
||||
|
||||
- **Deutsch (de)** - Standardsprache
|
||||
- **Englisch (en)**
|
||||
|
||||
## URL-Struktur
|
||||
|
||||
Alle Routen sind lokalisiert:
|
||||
|
||||
- `http://localhost:3000/` → Redirect zu `/de` (Standard)
|
||||
- `http://localhost:3000/de` → Deutsche Version
|
||||
- `http://localhost:3000/en` → Englische Version
|
||||
- `http://localhost:3000/de/admin` → Admin-Dashboard (Deutsch)
|
||||
- `http://localhost:3000/de/Rock` → Rock Genre (Deutsch)
|
||||
- `http://localhost:3000/de/special/Weihnachtslieder` → Special (Deutsch)
|
||||
|
||||
## Architektur
|
||||
|
||||
### Verzeichnisstruktur
|
||||
|
||||
```
|
||||
app/
|
||||
[locale]/ # Lokalisierte Routen
|
||||
layout.tsx # Root Layout mit i18n Provider
|
||||
page.tsx # Homepage
|
||||
admin/
|
||||
page.tsx # Admin Dashboard
|
||||
[genre]/
|
||||
page.tsx # Genre-spezifische Seite
|
||||
special/
|
||||
[name]/
|
||||
page.tsx # Special-Seite
|
||||
|
||||
i18n/
|
||||
request.ts # next-intl Konfiguration
|
||||
|
||||
messages/
|
||||
de.json # Deutsche Übersetzungen
|
||||
en.json # Englische Übersetzungen
|
||||
|
||||
lib/
|
||||
i18n.ts # Helper-Funktionen für lokalisierte DB-Werte
|
||||
navigation.ts # Lokalisierte Navigation (Link, useRouter, etc.)
|
||||
```
|
||||
|
||||
### Übersetzungsdateien
|
||||
|
||||
Die Übersetzungen sind in JSON-Dateien unter `messages/` organisiert:
|
||||
|
||||
```json
|
||||
{
|
||||
"Common": {
|
||||
"loading": "Laden...",
|
||||
"error": "Ein Fehler ist aufgetreten"
|
||||
},
|
||||
"Game": {
|
||||
"play": "Abspielen",
|
||||
"pause": "Pause",
|
||||
"won": "Gewonnen!"
|
||||
},
|
||||
"Home": {
|
||||
"welcome": "Willkommen bei Hördle"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Datenbank-Schema
|
||||
|
||||
Die folgenden Modelle unterstützen mehrsprachige Felder:
|
||||
|
||||
#### Genre
|
||||
- `name`: JSON `{ "de": "Rock", "en": "Rock" }`
|
||||
- `subtitle`: JSON `{ "de": "Klassischer Rock", "en": "Classic Rock" }`
|
||||
|
||||
#### Special
|
||||
- `name`: JSON `{ "de": "Weihnachtslieder", "en": "Christmas Songs" }`
|
||||
- `subtitle`: JSON `{ "de": "Festliche Musik", "en": "Festive Music" }`
|
||||
|
||||
#### News
|
||||
- `title`: JSON `{ "de": "Neues Feature", "en": "New Feature" }`
|
||||
- `content`: JSON `{ "de": "Markdown Inhalt...", "en": "Markdown content..." }`
|
||||
|
||||
### Helper-Funktionen
|
||||
|
||||
#### `getLocalizedValue(value, locale, fallback?)`
|
||||
|
||||
Extrahiert den lokalisierten Wert aus einem JSON-Objekt:
|
||||
|
||||
```typescript
|
||||
import { getLocalizedValue } from '@/lib/i18n';
|
||||
|
||||
const genreName = getLocalizedValue(genre.name, 'de'); // "Rock"
|
||||
const genreNameEn = getLocalizedValue(genre.name, 'en'); // "Rock"
|
||||
```
|
||||
|
||||
**Fallback-Verhalten:**
|
||||
1. Versucht die angeforderte Locale (`de` oder `en`)
|
||||
2. Fallback zu `de` falls nicht vorhanden
|
||||
3. Fallback zu `en` falls nicht vorhanden
|
||||
4. Fallback zum ersten verfügbaren Schlüssel
|
||||
5. Fallback zum übergebenen `fallback`-Parameter
|
||||
|
||||
#### `createLocalizedObject(de, en?)`
|
||||
|
||||
Erstellt ein lokalisiertes Objekt:
|
||||
|
||||
```typescript
|
||||
import { createLocalizedObject } from '@/lib/i18n';
|
||||
|
||||
const name = createLocalizedObject('Rock', 'Rock');
|
||||
// { de: "Rock", en: "Rock" }
|
||||
```
|
||||
|
||||
## Verwendung in Komponenten
|
||||
|
||||
### Server Components
|
||||
|
||||
```typescript
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { getLocalizedValue } from '@/lib/i18n';
|
||||
|
||||
export default async function Page({ params }: { params: { locale: string } }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations('Home');
|
||||
|
||||
const genreName = getLocalizedValue(genre.name, locale);
|
||||
|
||||
return <h1>{t('welcome')}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
### Client Components
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export default function Game() {
|
||||
const t = useTranslations('Game');
|
||||
const locale = useLocale();
|
||||
|
||||
return <button>{t('play')}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
Verwende die lokalisierte Navigation aus `lib/navigation.ts`:
|
||||
|
||||
```typescript
|
||||
import { Link } from '@/lib/navigation';
|
||||
|
||||
// Automatisch lokalisiert
|
||||
<Link href="/admin">Admin</Link>
|
||||
<Link href="/Rock">Rock</Link>
|
||||
```
|
||||
|
||||
## Admin-Interface
|
||||
|
||||
Das Admin-Dashboard unterstützt mehrsprachige Eingaben:
|
||||
|
||||
1. **Sprach-Tabs:** Wechsle zwischen `DE` und `EN` Tabs
|
||||
2. **Genre/Special/News:** Alle Felder können in beiden Sprachen bearbeitet werden
|
||||
3. **Vorschau:** Sieh dir die lokalisierte Version direkt an
|
||||
|
||||
### Beispiel: Genre erstellen
|
||||
|
||||
1. Öffne `/de/admin`
|
||||
2. Wähle den `DE` Tab
|
||||
3. Gib Name und Subtitle ein
|
||||
4. Wechsle zum `EN` Tab
|
||||
5. Gib die englischen Übersetzungen ein
|
||||
6. Speichere
|
||||
|
||||
## Migration bestehender Daten
|
||||
|
||||
Bestehende Daten werden automatisch migriert:
|
||||
|
||||
1. **Migration `20251128131405_add_i18n_columns`:** Fügt neue JSON-Spalten hinzu
|
||||
2. **Migration `20251128132806_switch_to_json_columns`:** Konvertiert String-Spalten zu JSON
|
||||
|
||||
**Wichtig:** Alte String-Werte werden automatisch in beide Sprachen kopiert:
|
||||
- `"Rock"` → `{ "de": "Rock", "en": "Rock" }`
|
||||
|
||||
## Proxy
|
||||
|
||||
Der Proxy (`proxy.ts`) leitet Anfragen automatisch um:
|
||||
|
||||
- `/` → `/de` (Standard)
|
||||
- Ungültige Locales → 404
|
||||
- Validiert Locale-Parameter
|
||||
|
||||
## Sprachumschalter
|
||||
|
||||
Die `LanguageSwitcher`-Komponente ermöglicht Nutzern, zwischen Sprachen zu wechseln:
|
||||
|
||||
```typescript
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher';
|
||||
|
||||
<LanguageSwitcher />
|
||||
```
|
||||
|
||||
Die aktuelle Route bleibt erhalten, nur die Locale ändert sich:
|
||||
- `/de/admin` → `/en/admin`
|
||||
- `/de/Rock` → `/en/Rock`
|
||||
|
||||
## API-Endpunkte
|
||||
|
||||
API-Routen unterstützen einen optionalen `locale`-Parameter:
|
||||
|
||||
```typescript
|
||||
GET /api/genres?locale=de
|
||||
GET /api/specials?locale=en
|
||||
GET /api/news?locale=de
|
||||
```
|
||||
|
||||
Falls kein `locale` angegeben wird, wird `de` als Standard verwendet.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Immer `getLocalizedValue` verwenden
|
||||
|
||||
❌ **Falsch:**
|
||||
```typescript
|
||||
<span>{genre.name}</span> // Rendert { de: "...", en: "..." }
|
||||
```
|
||||
|
||||
✅ **Richtig:**
|
||||
```typescript
|
||||
<span>{getLocalizedValue(genre.name, locale)}</span>
|
||||
```
|
||||
|
||||
### 2. Übersetzungsschlüssel konsistent benennen
|
||||
|
||||
Verwende Namespaces für bessere Organisation:
|
||||
- `Common.*` - Allgemeine UI-Elemente
|
||||
- `Game.*` - Spiel-spezifische Texte
|
||||
- `Home.*` - Homepage-Texte
|
||||
- `Navigation.*` - Navigations-Elemente
|
||||
|
||||
### 3. Fallbacks definieren
|
||||
|
||||
Immer einen Fallback-Wert angeben:
|
||||
|
||||
```typescript
|
||||
const name = getLocalizedValue(genre.name, locale, 'Unbekannt');
|
||||
```
|
||||
|
||||
### 4. Neue Übersetzungen hinzufügen
|
||||
|
||||
1. Füge den Schlüssel zu `messages/de.json` hinzu
|
||||
2. Füge den Schlüssel zu `messages/en.json` hinzu
|
||||
3. Verwende `useTranslations('Namespace')` oder `getTranslations('Namespace')`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 404-Fehler auf `/de` oder `/en`
|
||||
|
||||
**Problem:** Route wird nicht gefunden.
|
||||
|
||||
**Lösung:**
|
||||
1. Überprüfe, ob `proxy.ts` korrekt konfiguriert ist
|
||||
2. Stelle sicher, dass `app/[locale]/layout.tsx` existiert
|
||||
3. Prüfe die `i18n/request.ts` Konfiguration
|
||||
|
||||
### "Objects are not valid as a React child"
|
||||
|
||||
**Problem:** Ein JSON-Objekt wird direkt gerendert statt des lokalisierten Werts.
|
||||
|
||||
**Lösung:**
|
||||
Verwende `getLocalizedValue()`:
|
||||
|
||||
```typescript
|
||||
// ❌ Falsch
|
||||
<span>{genre.name}</span>
|
||||
|
||||
// ✅ Richtig
|
||||
<span>{getLocalizedValue(genre.name, locale)}</span>
|
||||
```
|
||||
|
||||
### Übersetzungen werden nicht angezeigt
|
||||
|
||||
**Problem:** Texte erscheinen als Schlüssel (z.B. `"Game.play"`).
|
||||
|
||||
**Lösung:**
|
||||
1. Überprüfe, ob der Übersetzungsschlüssel in `messages/de.json` und `messages/en.json` existiert
|
||||
2. Stelle sicher, dass der Namespace korrekt ist: `useTranslations('Game')` für `Game.play`
|
||||
3. Prüfe die JSON-Syntax auf Fehler
|
||||
|
||||
### Admin-Interface zeigt Objekte statt Text
|
||||
|
||||
**Problem:** In Dropdowns oder Listen werden `{ de: "...", en: "..." }` angezeigt.
|
||||
|
||||
**Lösung:**
|
||||
Verwende `getLocalizedValue()` in allen Render-Funktionen:
|
||||
|
||||
```typescript
|
||||
// ❌ Falsch
|
||||
<option value={s.id}>{s.name}</option>
|
||||
|
||||
// ✅ Richtig
|
||||
<option value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
|
||||
```
|
||||
|
||||
## Erweiterung um weitere Sprachen
|
||||
|
||||
Um eine neue Sprache hinzuzufügen (z.B. Französisch):
|
||||
|
||||
1. **Übersetzungsdatei erstellen:**
|
||||
```bash
|
||||
cp messages/de.json messages/fr.json
|
||||
```
|
||||
|
||||
2. **Übersetzungen hinzufügen:**
|
||||
Bearbeite `messages/fr.json` mit französischen Übersetzungen
|
||||
|
||||
3. **Locale zur Konfiguration hinzufügen:**
|
||||
- `i18n/request.ts`: `const locales = ['en', 'de', 'fr'];`
|
||||
- `proxy.ts`: `locales: ['en', 'de', 'fr']`
|
||||
- `lib/navigation.ts`: `export const locales = ['de', 'en', 'fr'] as const;`
|
||||
|
||||
4. **Layout aktualisieren:**
|
||||
```typescript
|
||||
// app/[locale]/layout.tsx
|
||||
if (!['en', 'de', 'fr'].includes(locale)) {
|
||||
notFound();
|
||||
}
|
||||
```
|
||||
|
||||
5. **LanguageSwitcher erweitern:**
|
||||
Füge einen Button für `fr` hinzu
|
||||
|
||||
6. **Datenbank-Migration:**
|
||||
Bestehende Daten behalten ihre Struktur, neue Einträge können optional `fr` enthalten
|
||||
|
||||
## Weitere Ressourcen
|
||||
|
||||
- [next-intl Dokumentation](https://next-intl-docs.vercel.app/)
|
||||
- [Next.js App Router i18n](https://nextjs.org/docs/app/building-your-application/routing/internationalization)
|
||||
|
||||
206
docs/TROUBLESHOOTING.md
Normal file
206
docs/TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
## Application Error: "a server-side exception has occurred"
|
||||
|
||||
Dieser Fehler tritt auf, wenn die Next.js-Anwendung auf dem Server einen Fehler hat.
|
||||
|
||||
### ⚠️ Datenbank-Berechtigungen (wenn DB von anderem Server kopiert wurde)
|
||||
|
||||
**Symptom**: Application Error nach dem Kopieren einer Datenbank von einem anderen Server
|
||||
|
||||
**Ursache**: SQLite benötigt Schreibrechte auf:
|
||||
- Die Datenbankdatei selbst (`prod.db`)
|
||||
- Das Datenbankverzeichnis (für temporäre Dateien wie `-wal`, `-shm`)
|
||||
|
||||
**Sofort-Lösung (auf dem Server ausführen)**:
|
||||
```bash
|
||||
# 1. Setze Berechtigungen für Datenbankverzeichnis und Datei
|
||||
chmod 775 ./data
|
||||
chmod 664 ./data/prod.db
|
||||
|
||||
# 2. Falls temporäre SQLite-Dateien existieren, auch diese:
|
||||
chmod 664 ./data/*.db-wal 2>/dev/null || true
|
||||
chmod 664 ./data/*.db-shm 2>/dev/null || true
|
||||
|
||||
# 3. Oder verwende das Fix-Skript:
|
||||
./scripts/fix-database-permissions.sh
|
||||
|
||||
# 4. Container neu starten
|
||||
docker compose restart hoerdle
|
||||
|
||||
# 5. Logs prüfen
|
||||
docker logs hoerdle --tail=50
|
||||
```
|
||||
|
||||
**Warum passiert das?**
|
||||
- Wenn du eine Datenbankdatei von einem anderen Server kopierst, behält sie die ursprünglichen Berechtigungen
|
||||
- SQLite muss Schreibrechte haben, um zu funktionieren
|
||||
- Auch das Verzeichnis braucht Schreibrechte (für SQLite-WAL-Modus)
|
||||
|
||||
### Sofort-Diagnose (auf dem Server ausführen)
|
||||
|
||||
```bash
|
||||
# 1. Container-Logs prüfen (die wichtigste Information!)
|
||||
docker logs hoerdle --tail=100
|
||||
|
||||
# 2. Container-Status prüfen
|
||||
docker ps | grep hoerdle
|
||||
|
||||
# 3. Prüfe ob Datenbank existiert
|
||||
docker exec hoerdle ls -lh /app/data/prod.db
|
||||
|
||||
# 4. Prüfe ob Server auf Port 3000 antwortet (intern)
|
||||
docker exec hoerdle curl -f http://localhost:3000/api/daily
|
||||
|
||||
# 5. Prüfe Health Check
|
||||
docker inspect hoerdle --format='{{json .State.Health}}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Häufige Ursachen und Lösungen
|
||||
|
||||
#### 1. Datenbankfehler / Migrationen fehlgeschlagen
|
||||
|
||||
**Symptom**: Logs zeigen Prisma-Fehler oder "database locked"
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Container-Logs prüfen
|
||||
docker logs hoerdle | grep -i "migration\|database\|prisma"
|
||||
|
||||
# Falls Migrationen fehlgeschlagen sind:
|
||||
docker compose restart hoerdle
|
||||
|
||||
# Bei persistierenden Problemen: Datenbank-Backup prüfen
|
||||
ls -lh ./backups/
|
||||
```
|
||||
|
||||
#### 2. Container läuft nicht oder ist crashed
|
||||
|
||||
**Symptom**: Container existiert nicht oder Status zeigt "Exited"
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Container-Status prüfen
|
||||
docker ps -a | grep hoerdle
|
||||
|
||||
# Container neu starten
|
||||
docker compose up -d
|
||||
|
||||
# Falls Container nicht startet, Logs prüfen
|
||||
docker logs hoerdle --tail=200
|
||||
```
|
||||
|
||||
#### 3. Caddy kann Container nicht erreichen
|
||||
|
||||
**Symptom**: 502 Bad Gateway oder "connection refused" in Caddy-Logs
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Prüfe ob hoerdle Container läuft
|
||||
docker ps | grep hoerdle
|
||||
|
||||
# Prüfe Netzwerk
|
||||
docker network inspect hoerdle_default
|
||||
|
||||
# Prüfe Caddy-Logs
|
||||
docker logs hoerdle-caddy --tail=50
|
||||
|
||||
# Stelle sicher, dass Caddyfile Port 3000 verwendet (nicht 3010!)
|
||||
grep "reverse_proxy" Caddyfile
|
||||
```
|
||||
|
||||
#### 4. Fehlende Umgebungsvariablen
|
||||
|
||||
**Symptom**: Logs zeigen undefined variables
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Prüfe wichtige Umgebungsvariablen
|
||||
docker exec hoerdle env | grep -E "DATABASE_URL|NODE_ENV"
|
||||
|
||||
# Prüfe .env Datei (falls vorhanden)
|
||||
cat .env | grep DATABASE_URL
|
||||
```
|
||||
|
||||
#### 5. Build-Fehler oder fehlerhafte Dateien
|
||||
|
||||
**Symptom**: Container startet, aber App crasht sofort
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Container komplett neu bauen
|
||||
docker compose down
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
|
||||
# Prüfe Build-Logs
|
||||
docker compose build 2>&1 | tee build.log
|
||||
```
|
||||
|
||||
### Detaillierte Log-Analyse
|
||||
|
||||
```bash
|
||||
# Alle Fehler in Logs finden
|
||||
docker logs hoerdle 2>&1 | grep -i -E "error|exception|fatal|panic" | tail -50
|
||||
|
||||
# Prisma-spezifische Fehler
|
||||
docker logs hoerdle 2>&1 | grep -i prisma | tail -20
|
||||
|
||||
# Next.js-spezifische Fehler
|
||||
docker logs hoerdle 2>&1 | grep -i "next\|react" | tail -20
|
||||
```
|
||||
|
||||
### Netzwerk-Debugging
|
||||
|
||||
```bash
|
||||
# Teste Verbindung von Caddy zu Hördle
|
||||
docker exec hoerdle-caddy wget -O- http://hoerdle:3000/api/daily
|
||||
|
||||
# Prüfe alle Container im Netzwerk
|
||||
docker network inspect hoerdle_default --format='{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'
|
||||
```
|
||||
|
||||
### Datenbank-Debugging
|
||||
|
||||
```bash
|
||||
# Prüfe Datenbank-Integrität
|
||||
docker exec hoerdle npx prisma db pull
|
||||
|
||||
# Prüfe Datenbank-Struktur
|
||||
docker exec hoerdle npx prisma studio &
|
||||
# (dann Browser öffnen - erfordert X11 forwarding oder lokalen Zugriff)
|
||||
```
|
||||
|
||||
### Quick-Fix: Vollständiger Neustart
|
||||
|
||||
Wenn nichts anderes hilft:
|
||||
|
||||
```bash
|
||||
# 1. Backup erstellen
|
||||
cp ./data/prod.db ./backups/prod_$(date +%Y%m%d_%H%M%S).db
|
||||
|
||||
# 2. Container stoppen
|
||||
docker compose down
|
||||
|
||||
# 3. Container neu starten
|
||||
docker compose up -d
|
||||
|
||||
# 4. Logs beobachten
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
## Bei weiterem Bedarf
|
||||
|
||||
Sammle folgende Informationen für weitere Hilfe:
|
||||
|
||||
```bash
|
||||
echo "=== Container Status ===" && \
|
||||
docker ps -a | grep hoerdle && \
|
||||
echo -e "\n=== Letzte 50 Log-Zeilen ===" && \
|
||||
docker logs hoerdle --tail=50 && \
|
||||
echo -e "\n=== Fehler in Logs ===" && \
|
||||
docker logs hoerdle 2>&1 | grep -i error | tail -20
|
||||
```
|
||||
|
||||
Kopiere die vollständige Ausgabe und sende sie weiter.
|
||||
|
||||
99
docs/WHITE_LABEL.md
Normal file
99
docs/WHITE_LABEL.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# White Labeling Guide
|
||||
|
||||
This application is designed to be easily white-labeled. You can customize the branding, colors, and configuration without modifying the core code.
|
||||
|
||||
## Configuration
|
||||
|
||||
The application is configured via environment variables. You can set these in a `.env` or `.env.local` file.
|
||||
|
||||
### Branding
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `NEXT_PUBLIC_APP_NAME` | The name of the application. | `Hördle` |
|
||||
| `NEXT_PUBLIC_APP_DESCRIPTION` | The description used in metadata. | `Daily music guessing game...` |
|
||||
| `NEXT_PUBLIC_DOMAIN` | The domain name (used for sharing). | `hoerdle.elpatron.me` |
|
||||
| `NEXT_PUBLIC_TWITTER_HANDLE` | Twitter handle for metadata. | `@elpatron` |
|
||||
|
||||
### Analytics (Plausible)
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` | The domain to track in Plausible. | `hoerdle.elpatron.me` |
|
||||
| `NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC` | The URL of the Plausible script. | `https://plausible.elpatron.me/js/script.js` |
|
||||
|
||||
### Credits
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `NEXT_PUBLIC_CREDITS_ENABLED` | Enable/disable footer credits (`true`/`false`). | `true` |
|
||||
| `NEXT_PUBLIC_CREDITS_TEXT` | Text before the link. | `Vibe coded with ☕ and 🍺 by` |
|
||||
| `NEXT_PUBLIC_CREDITS_LINK_TEXT` | Text of the link. | `@elpatron@digitalcourage.social` |
|
||||
| `NEXT_PUBLIC_CREDITS_LINK_URL` | URL of the link. | `https://digitalcourage.social/@elpatron` |
|
||||
|
||||
## Theming
|
||||
|
||||
The application uses CSS variables for theming. You can override these variables in your own CSS file or by modifying `app/globals.css`.
|
||||
|
||||
### Key Colors
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `--primary` | Main action color (buttons). | `#000000` |
|
||||
| `--secondary` | Secondary actions. | `#4b5563` |
|
||||
| `--accent` | Accent color. | `#667eea` |
|
||||
| `--success` | Success state (correct guess). | `#22c55e` |
|
||||
| `--danger` | Error state (wrong guess). | `#ef4444` |
|
||||
| `--warning` | Warning state (stars). | `#ffc107` |
|
||||
| `--muted` | Muted backgrounds. | `#f3f4f6` |
|
||||
|
||||
### Example: Red Theme
|
||||
|
||||
To create a red-themed version, add this to your CSS:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--primary: #dc2626;
|
||||
--accent: #ef4444;
|
||||
--accent-gradient: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
|
||||
}
|
||||
```
|
||||
|
||||
## Assets
|
||||
|
||||
To replace the logo and icons:
|
||||
1. Replace `public/favicon.ico`.
|
||||
2. Replace `public/icon.png` (if it exists).
|
||||
3. Update `app/manifest.ts` if you have custom icon paths.
|
||||
3. Update `app/manifest.ts` if you have custom icon paths.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
When deploying with Docker, please note that **Next.js inlines `NEXT_PUBLIC_` environment variables at build time**.
|
||||
|
||||
This means you cannot simply change the environment variables in `docker-compose.yml` and restart the container to change the branding. You must **rebuild the image**.
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
1. Create a `.env` file with your custom configuration:
|
||||
```bash
|
||||
NEXT_PUBLIC_APP_NAME="My Music Game"
|
||||
NEXT_PUBLIC_THEME_COLOR="#ff0000"
|
||||
# ... other variables
|
||||
```
|
||||
|
||||
2. Ensure your `docker-compose.yml` passes these variables as build arguments (already configured in `docker-compose.example.yml`):
|
||||
```yaml
|
||||
services:
|
||||
hoerdle:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME}
|
||||
# ...
|
||||
```
|
||||
|
||||
3. Build and start the container:
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
55
docs/walkthrough.md
Normal file
55
docs/walkthrough.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Genre/Tag System Implementation Walkthrough
|
||||
|
||||
## Overview
|
||||
Implemented a comprehensive Genre/Tag system for Hördle, allowing songs to be categorized and users to play genre-specific daily puzzles.
|
||||
|
||||
## Changes
|
||||
|
||||
### Database
|
||||
- **New Model**: `Genre` (id, name, songs, dailyPuzzles).
|
||||
- **Updated Model**: `Song` (added M-N relation to `Genre`).
|
||||
- **Updated Model**: `DailyPuzzle` (added optional `genreId`, updated unique constraint to `[date, genreId]`).
|
||||
|
||||
### Backend API
|
||||
- **`app/api/genres/route.ts`**: New endpoints for GET (list) and POST (create) and DELETE genres.
|
||||
- **`app/api/songs/route.ts`**: Updated to handle genre assignment (POST/PUT) and retrieval (GET).
|
||||
- **`app/api/daily/route.ts`**: Updated to support `?genre=<name>` query parameter.
|
||||
- **`lib/dailyPuzzle.ts`**: Shared logic for fetching/creating daily puzzles (Global or Genre-specific).
|
||||
|
||||
### Frontend (Admin)
|
||||
- **Genre Management**: Create and delete genres.
|
||||
- **Song Assignment**: Assign genres during upload and edit.
|
||||
- **Post-Upload Workflow**: Prompt to assign genres immediately after upload.
|
||||
- **Song List**: Display assigned genres in the table.
|
||||
|
||||
### Frontend (User)
|
||||
- **Genre Selection**: Links on the main page to switch between Global and Genre-specific games.
|
||||
- **Game Logic**: Refactored to support independent game states per genre (localStorage keys: `hoerdle_game_state_<genre>`).
|
||||
- **Dynamic Route**: `app/[genre]/page.tsx` for genre-specific URLs.
|
||||
- **Sharing**: Share text now includes the genre name.
|
||||
|
||||
### Deployment
|
||||
- **Auto-Migration**: Added `scripts/docker-entrypoint.sh` to run `prisma migrate deploy` on startup.
|
||||
- **Dockerfile**: Updated to use the entrypoint script.
|
||||
- **Dependencies**: Moved `prisma` to `dependencies` in `package.json`.
|
||||
|
||||
## Verification Results
|
||||
|
||||
### Automated Build
|
||||
- `npm run build` passed successfully.
|
||||
- `npx prisma generate` passed.
|
||||
|
||||
### Manual Verification Steps (Recommended)
|
||||
1. **Admin Dashboard**:
|
||||
* Go to `/admin`.
|
||||
* Create a new genre (e.g., "Rock").
|
||||
* Upload a song and assign "Rock" to it.
|
||||
* Edit an existing song and assign "Rock".
|
||||
2. **User Interface**:
|
||||
* Go to `/`. Verify "Global" game works.
|
||||
* Click "Rock". Verify URL changes to `/Rock`.
|
||||
* Play the "Rock" game. Verify it picks a song tagged with "Rock".
|
||||
* Verify stats are separate for Global and Rock.
|
||||
3. **Deployment**:
|
||||
* Deploy to Docker.
|
||||
* Verify migrations run automatically on startup.
|
||||
Reference in New Issue
Block a user