Mehrstufiges Image: gebaut wird mit allen Abhaengigkeiten, ausgeliefert wird nur die Laufzeit. Migrationen laufen beim Containerstart. Der App-Port wird bewusst nicht nach aussen gemappt - davor gehoert ein Reverse Proxy, der HTTPS terminiert. Der Backup-Service schreibt taeglich einen pg_dump und raeumt Dumps aelter als 14 Tage weg.
179 lines
7.1 KiB
Markdown
179 lines
7.1 KiB
Markdown
# CC-Manager
|
|
|
|
Selbst gehostete Web-App zur Verwaltung von Custom Characters (CCs) auf einem SCP-RP-Server.
|
|
Sie führt pro Spieler ein Konto über gekaufte Ansprüche und deren Verbrauch, damit die Frage
|
|
„darf der gerade einen neuen CC?" nicht mehr von Hand abgeglichen werden muss.
|
|
|
|
## Das Prinzip
|
|
|
|
Jeder Spieler hat drei Guthaben-Töpfe:
|
|
|
|
| Topf | Wird gefüllt durch | Wird verbraucht durch |
|
|
|------|--------------------|-----------------------|
|
|
| CC-Guthaben | jedes CC-Paket (+1) | ein neuer CC |
|
|
| Redesign-Guthaben (bezahlt) | jedes CC-Paket (+1, immer inklusive) und jedes Redesign-Paket (+1) | ein Redesign |
|
|
| Kulanz-Redesign | manuell eingetragene Kulanz (+1) | ein Redesign |
|
|
|
|
Zwei Regeln sind fest verdrahtet:
|
|
|
|
1. **Ein CC-Paket erzeugt beide Guthaben.** Der inklusive Redesign wird automatisch mitgebucht,
|
|
in derselben Transaktion und mit gemeinsamer Gruppen-ID — ein Storno nimmt beide zurück.
|
|
2. **Inaktive CCs kosten unterschiedlich.** Wurde ein CC nur wegen Spieler-Inaktivität vom Server
|
|
entfernt, kommt er gratis zurück. Wurde er redesignt und existiert im Original nicht mehr,
|
|
kostet die Reaktivierung ein Redesign- oder CC-Guthaben.
|
|
|
|
Guthaben werden als **Kontobuch** geführt: jede Bewegung ist eine eigene Zeile, der Stand eines
|
|
Topfes ist deren Summe. Das macht jeden Stand nachvollziehbar und Korrekturen sauber — storniert
|
|
wird per Gegenbuchung, nie durch Löschen. Ein überzogenes Konto ist erlaubt (kommt vor, wenn ein
|
|
Redesign vor dem zugehörigen Paket erfasst wird), wird aber rot markiert.
|
|
|
|
Ein Redesign legt einen **neuen** CC-Datensatz an und verkettet ihn mit dem alten. So bleibt die
|
|
Kette „A → B → C" sichtbar und die Reaktivierungsregel greift automatisch richtig.
|
|
|
|
## Schnellstart
|
|
|
|
Vorausgesetzt wird Docker mit Compose-Plugin (oder DockGe).
|
|
|
|
```bash
|
|
git clone https://git.harlelux.de/Harlekin/CC-Manager.git
|
|
cd CC-Manager
|
|
cp .env.example .env
|
|
```
|
|
|
|
In der `.env` mindestens `DB_PASSWORD` und `SESSION_SECRET` setzen:
|
|
|
|
```bash
|
|
openssl rand -base64 48 # für SESSION_SECRET
|
|
```
|
|
|
|
Dann starten:
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
docker compose logs -f app
|
|
```
|
|
|
|
Beim ersten Start legt die App einen Administrator an. Ist `BOOTSTRAP_ADMIN_PASSWORD` leer,
|
|
erzeugt sie ein Einmal-Passwort und schreibt es **einmalig** ins Log — dort steht es in der
|
|
Ausgabe oben. Beim ersten Login ist ein Passwortwechsel Pflicht; bis dahin sind alle anderen
|
|
Seiten gesperrt.
|
|
|
|
Für DockGe: Repo-Inhalt als Stack anlegen, `.env` daneben, fertig. Datenbank-Migrationen laufen
|
|
bei jedem Containerstart automatisch mit.
|
|
|
|
## HTTPS
|
|
|
|
Der App-Port wird bewusst **nicht** nach außen gemappt. Davor gehört ein Reverse Proxy
|
|
(Nginx Proxy Manager, Traefik oder Caddy), der HTTPS terminiert und intern auf `app:3000`
|
|
weiterleitet. So ist nur Port 443 offen und die Zertifikate laufen über Let's Encrypt.
|
|
|
|
Läuft die App hinter einem Proxy, muss `TRUST_PROXY=true` gesetzt bleiben — sonst landen im
|
|
Audit-Log die Proxy-IPs statt der echten. In Produktion werden Cookies automatisch auf `Secure`
|
|
gesetzt; nur zum Testen ohne TLS lässt sich das über `COOKIE_SECURE=false` abschalten.
|
|
|
|
## Backups
|
|
|
|
Der `backup`-Service im Stack schreibt beim Start und danach alle 24 Stunden einen
|
|
`pg_dump` nach `./backups/` und räumt Dumps älter als 14 Tage weg. Die Daten bilden bezahlte
|
|
Ansprüche ab — das Verzeichnis gehört auf eine Sicherung außerhalb des Hosts.
|
|
|
|
Wiederherstellen:
|
|
|
|
```bash
|
|
gunzip -c backups/ccmanager-2025-01-15-0300.sql.gz | \
|
|
docker compose exec -T db psql -U ccuser -d ccmanager
|
|
```
|
|
|
|
## Import
|
|
|
|
Kaufdaten lassen sich als CSV oder JSON hochladen; das Format ist nicht festgelegt. Der Ablauf:
|
|
|
|
1. Datei hochladen — Spalten und Beispielzeilen werden angezeigt.
|
|
2. Spalten zuordnen (Spieler-Kennung, Produktname, optional Bestell-ID und Datum). Die üblichen
|
|
Spaltennamen werden vorausgewählt.
|
|
3. **Vorschau** prüfen: für jede Zeile steht da, was passieren würde.
|
|
4. Buchen.
|
|
|
|
Welcher Produktname welches Guthaben ergibt, steht in der Mapping-Tabelle unter *Verwaltung →
|
|
Produkt-Mappings* (Teiltreffer, Groß-/Kleinschreibung egal). Neue Produkte lassen sich dort
|
|
jederzeit ergänzen, ohne den Code anzufassen.
|
|
|
|
Wird eine Bestell-ID zugeordnet, bucht ein zweiter Import dieselben Käufe **nicht** erneut —
|
|
Dubletten werden sowohl gegen bereits gebuchte Bestellungen als auch innerhalb der Datei erkannt.
|
|
Ohne Bestell-ID entfällt dieser Schutz; darauf weist das Formular hin.
|
|
|
|
Kulanz-Redesigns tauchen in keinem Shop-Export auf und werden immer von Hand eingetragen.
|
|
|
|
## Rollen
|
|
|
|
Es gibt **keine öffentliche Registrierung**. Konten legt ausschließlich ein Administrator an.
|
|
|
|
- **Viewer** — darf nur lesen.
|
|
- **Editor** — Spieler anlegen, Guthaben eintragen, CCs erstellen, redesignen, reaktivieren.
|
|
- **Admin** — zusätzlich Import, Nutzerverwaltung, Produkt-Mappings, Audit-Log und Korrekturbuchungen.
|
|
|
|
Jede ändernde Aktion landet im Audit-Log. Für Administratoren lässt sich unter *Mein Konto*
|
|
eine Zwei-Faktor-Anmeldung per TOTP einrichten.
|
|
|
|
## Entwicklung
|
|
|
|
Gebraucht werden Node 20+ und eine erreichbare PostgreSQL-Instanz.
|
|
|
|
```bash
|
|
npm install
|
|
cp server/.env.example server/.env # DATABASE_URL auf die lokale DB zeigen lassen
|
|
npm run migrate:dev
|
|
npm run dev # API auf :3000, Frontend auf :5173
|
|
```
|
|
|
|
Das Frontend leitet `/api` im Entwicklungsbetrieb an den API-Port weiter, Anmeldung und Cookies
|
|
funktionieren also wie in Produktion.
|
|
|
|
Die Tests brauchen **keine** laufende Datenbank — sie starten eine Postgres-Instanz im Speicher:
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
Abgedeckt sind die Guthaben-Regeln, der CC-Lebenszyklus samt Reaktivierungslogik, Korrekturen
|
|
und Stornos, Anmeldung mit CSRF- und Rollenprüfung sowie der Import mit Dedup.
|
|
|
|
### Aufbau
|
|
|
|
```
|
|
server/
|
|
prisma/schema.prisma Datenmodell und Migrationen
|
|
src/services/ Fachlogik: Ledger, CC-Lebenszyklus, Import
|
|
src/routes/ HTTP-Endpunkte
|
|
src/auth/ Passwörter, Sitzungen, CSRF, Rollen, TOTP
|
|
test/ Integrationstests gegen eine echte Postgres-Instanz
|
|
web/
|
|
src/pages/ Login, Übersicht, Spieler-Detail, Import, Verwaltung
|
|
src/components/ Wiederverwendete Bausteine
|
|
```
|
|
|
|
Backend ist Fastify mit Prisma, Frontend eine React-SPA. Gebaut wird beides in ein einziges
|
|
Image: Fastify liefert die API und die fertigen Frontend-Dateien aus.
|
|
|
|
Nach Änderungen am Datenmodell:
|
|
|
|
```bash
|
|
npm run migrate:dev -- --name beschreibung-der-aenderung
|
|
```
|
|
|
|
## Sicherheit
|
|
|
|
- Passwörter mit Argon2id gehasht, Mindestlänge 12 Zeichen.
|
|
- Sitzungen serverseitig; im Cookie steht nur ein Token, in der Datenbank dessen Hash.
|
|
Cookie ist `httpOnly`, `SameSite=Lax` und in Produktion `Secure`.
|
|
- CSRF-Schutz für alle ändernden Anfragen über ein aus der Sitzung abgeleitetes Token.
|
|
- Alle Eingaben werden validiert, alle Datenbankzugriffe laufen parametrisiert über das ORM.
|
|
- Rate-Limit auf dem Login, generische Fehlermeldung ohne Hinweis auf existierende Konten.
|
|
- Sicherheits-Header inklusive Content-Security-Policy.
|
|
- Ein gesperrtes Konto verliert seine offenen Sitzungen sofort; der letzte aktive Administrator
|
|
kann sich nicht selbst entfernen.
|
|
|
|
## Lizenz
|
|
|
|
GPL-3.0-or-later, siehe [LICENSE](LICENSE).
|