tja
This commit is contained in:
@@ -17,14 +17,14 @@ Jeder Spieler hat drei Guthaben-Töpfe:
|
||||
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.
|
||||
in derselben Transaktion und mit gemeinsamer Gruppen-ID - ein Storno nimmt beide zurück.
|
||||
2. **Nur redesignte CCs kosten bei der Rückkehr.** Solange ein CC unverändert existiert, kommt er
|
||||
gratis zurück — gleich ob der Spieler inaktiv war oder die Einheit zeitweise entfernt wurde.
|
||||
gratis zurück - gleich ob der Spieler inaktiv war oder die Einheit zeitweise entfernt wurde.
|
||||
Wurde er dagegen redesignt, existiert das Original nicht mehr und die Reaktivierung kostet 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
|
||||
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.
|
||||
|
||||
@@ -33,7 +33,7 @@ Kette „A → B → C" sichtbar und die Reaktivierungsregel greift automatisch
|
||||
A zurück, ist A ein redesignter CC und kostet entsprechend.
|
||||
|
||||
Warum ein CC inaktiv wurde, lässt sich beim Deaktivieren als Klartext festhalten („Einheit bis
|
||||
März entfernt") — der Grund steht dann an der Karte, statt nur als Kategorie.
|
||||
März entfernt") - der Grund steht dann an der Karte, statt nur als Kategorie.
|
||||
|
||||
## Schnellstart
|
||||
|
||||
@@ -59,7 +59,7 @@ 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
|
||||
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.
|
||||
|
||||
@@ -73,7 +73,7 @@ Der App-Port wird bewusst **nicht** nach außen gemappt. Davor gehört ein Rever
|
||||
weiterleitet. So ist nur Port 443 offen und die Zertifikate laufen über Let's Encrypt.
|
||||
|
||||
Damit der Proxy die App erreicht, hängt der `app`-Service zusätzlich im Netzwerk des Proxys.
|
||||
Die `compose.yaml` erwartet es unter dem Namen `nginx_default` — so heißt es bei Nginx Proxy
|
||||
Die `compose.yaml` erwartet es unter dem Namen `nginx_default` - so heißt es bei Nginx Proxy
|
||||
Manager. Heißt es bei dir anders, setz `PROXY_NETWORK` in der `.env`:
|
||||
|
||||
```
|
||||
@@ -82,15 +82,15 @@ PROXY_NETWORK=mein-proxy-netz
|
||||
|
||||
Das Netzwerk muss existieren, bevor der Stack startet; angelegt wird es vom Proxy. Betreibst du
|
||||
gar keinen Proxy in Docker, entferne den `networks`-Block am Ende der `compose.yaml` samt der
|
||||
`networks`-Liste beim `app`-Service und mappe stattdessen einen Port — dann aber bitte HTTPS
|
||||
`networks`-Liste beim `app`-Service und mappe stattdessen einen Port - dann aber bitte HTTPS
|
||||
anderweitig davorsetzen.
|
||||
|
||||
> **Zu `$` in der `.env`:** Docker Compose deutet `$name` als Variable und ersetzt es durch
|
||||
> nichts, wenn es die Variable nicht gibt. Ein Passwort mit `$` kommt dadurch gekürzt in der App
|
||||
> an — ohne Fehler, nur mit einer leicht zu übersehenden Warnung. Entweder `$` vermeiden oder
|
||||
> an - ohne Fehler, nur mit einer leicht zu übersehenden Warnung. Entweder `$` vermeiden oder
|
||||
> als `$$` schreiben.
|
||||
|
||||
Läuft die App hinter einem Proxy, muss `TRUST_PROXY=true` gesetzt bleiben — sonst landen im
|
||||
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.
|
||||
|
||||
@@ -98,7 +98,7 @@ gesetzt; nur zum Testen ohne TLS lässt sich das über `COOKIE_SECURE=false` abs
|
||||
|
||||
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.
|
||||
Ansprüche ab - das Verzeichnis gehört auf eine Sicherung außerhalb des Hosts.
|
||||
|
||||
Wiederherstellen:
|
||||
|
||||
@@ -111,7 +111,7 @@ gunzip -c backups/ccmanager-2025-01-15-0300.sql.gz | \
|
||||
|
||||
Kaufdaten lassen sich als CSV oder JSON hochladen; das Format ist nicht festgelegt. Der Ablauf:
|
||||
|
||||
1. Datei hochladen — Spalten und Beispielzeilen werden angezeigt.
|
||||
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.
|
||||
@@ -121,7 +121,7 @@ Welcher Produktname welches Guthaben ergibt, steht in der Mapping-Tabelle unter
|
||||
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 —
|
||||
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.
|
||||
|
||||
@@ -132,7 +132,7 @@ Kulanz-Redesigns tauchen in keinem Shop-Export auf und werden immer von Hand ein
|
||||
> **Auf eine bestimmte Instanz zugeschnitten.** Dieser Teil liest Profile von
|
||||
> `gextension.senseigaming.de` und ist auf deren Seitenaufbau gebaut. Läuft bei euch eine
|
||||
> andere GExtension-Instanz, genügt `GEXTENSION_BASE_URL` in der `.env`. Nutzt ihr ein
|
||||
> *anderes* Shop- oder Spendensystem, funktioniert der Abgleich **nicht** — dafür müsste
|
||||
> *anderes* Shop- oder Spendensystem, funktioniert der Abgleich **nicht** - dafür müsste
|
||||
> `server/src/lib/gextension.ts` angepasst werden. Der Rest der App ist davon unabhängig und
|
||||
> läuft auch ohne diesen Teil; Pakete lassen sich immer von Hand oder per Datei-Import
|
||||
> eintragen.
|
||||
@@ -142,12 +142,12 @@ damit sie nicht abgetippt werden müssen.
|
||||
|
||||
**Was er tut:** Für jeden Spieler, der im CC-Manager erfasst ist *und* eine SteamID hinterlegt
|
||||
hat, ruft er die öffentliche Profilseite ab und liest die Paket-Liste. Auslösen lässt sich das
|
||||
auf der Übersicht (alle Spieler) oder auf einer Spielerseite (nur dieser eine) — jeweils als
|
||||
auf der Übersicht (alle Spieler) oder auf einer Spielerseite (nur dieser eine) - jeweils als
|
||||
Admin.
|
||||
|
||||
**Was er nicht tut:** Er sucht keine neuen Spieler. Wer nicht in der Datenbank steht, wird nie
|
||||
abgefragt, auch wenn er bei GExtension gekauft hat. Neue Käufer legst du von Hand an, danach
|
||||
findet der Abgleich ihre Pakete. Gebucht wird ebenfalls nichts von allein — eindeutige Treffer
|
||||
findet der Abgleich ihre Pakete. Gebucht wird ebenfalls nichts von allein - eindeutige Treffer
|
||||
landen in derselben Vorschau wie der Datei-Import und warten auf Bestätigung.
|
||||
|
||||
Erkannt wird anhand von Titel *und* Bereich: Nur der `SCP-RP`-Bereich führt CC-Pakete, und ein
|
||||
@@ -161,11 +161,11 @@ CC-Paket, gekauftes Redesign, Kulanz oder „nichts davon" einordnet. Bei CC-Pak
|
||||
automatisch auch das inklusive Redesign-Guthaben.
|
||||
|
||||
**Verbrauchszähler:** Meldet GExtension „CC-Paket oder Redesign genutzt | N Mal", wird das beim
|
||||
Spieler angezeigt, aber **nie** automatisch verrechnet — es ist eine Lesehilfe zum Abgleich mit
|
||||
Spieler angezeigt, aber **nie** automatisch verrechnet - es ist eine Lesehilfe zum Abgleich mit
|
||||
dem eigenen Ledger, mehr nicht.
|
||||
|
||||
Der Abgleich hält eine Sekunde Abstand zwischen den Anfragen und weist sich per User-Agent aus.
|
||||
Die Seite gehört dem Serverbetreiber, nicht dieser App — ein offizieller Datenzugang wäre der
|
||||
Die Seite gehört dem Serverbetreiber, nicht dieser App - ein offizieller Datenzugang wäre der
|
||||
sauberere Weg, falls er zu haben ist.
|
||||
|
||||
## Forums-Anträge
|
||||
@@ -178,32 +178,32 @@ Während der GExtension-Abgleich die **Käufe** liefert, steht der **Verbrauch**
|
||||
ein Antrag angenommen, abgelehnt, war es ein neuer CC oder ein Redesign? Unter *Anträge* liest
|
||||
die App die drei CC-Boards aus und sammelt die Threads.
|
||||
|
||||
**Entschieden wird ausschließlich von Hand.** Titel und Board werden bewusst nicht ausgewertet —
|
||||
**Entschieden wird ausschließlich von Hand.** Titel und Board werden bewusst nicht ausgewertet -
|
||||
Threads werden nicht immer zuverlässig verschoben, ein längst genehmigter Antrag kann noch im
|
||||
Antrags-Board liegen. Das aktuelle Board steht deshalb nur als Hinweis an der Zeile.
|
||||
|
||||
Pro Antrag wählt ein Admin: *angenommen als neuer CC*, *angenommen als Redesign* (mit Auswahl,
|
||||
welcher CC ersetzt wurde und woraus bezahlt wird), *angenommen als Reaktivierung* (ein inaktiver
|
||||
CC kehrt zurück — gratis, sofern er nicht redesignt wurde), *abgelehnt* oder *kein Antrag*. Die beiden
|
||||
angenommenen Fälle lösen exakt dieselben Buchungen aus wie die Knöpfe auf der Spielerseite —
|
||||
CC kehrt zurück - gratis, sofern er nicht redesignt wurde), *abgelehnt* oder *kein Antrag*. Die beiden
|
||||
angenommenen Fälle lösen exakt dieselben Buchungen aus wie die Knöpfe auf der Spielerseite -
|
||||
es gibt keinen zweiten Buchungsweg mit eigenen Regeln.
|
||||
|
||||
Zugeordnet wird über den Forumsnamen des Erstellers gegen das Feld *Forum-Name* beim Spieler —
|
||||
Zugeordnet wird über den Forumsnamen des Erstellers gegen das Feld *Forum-Name* beim Spieler -
|
||||
und über die **früheren Konten**, die auf der Spielerseite nachgetragen werden können. Wer seinen
|
||||
Steam- oder Forumsaccount gewechselt hat, behält so seine Ansprüche.
|
||||
**Threads ohne zuordenbaren Spieler werden gar nicht erst erfasst** — das Archiv reicht Jahre
|
||||
**Threads ohne zuordenbaren Spieler werden gar nicht erst erfasst** - das Archiv reicht Jahre
|
||||
zurück und enthält vieles, was kein CC-Antrag ist. Fehlt jemand, trag seinen Forumsnamen beim
|
||||
Spieler nach, dann nimmt ihn der nächste Abgleich auf.
|
||||
|
||||
Angepinnte Ankündigungen (Changelogs, Regelwerke) liegen mit in den Boards. Sie werden über ihre
|
||||
Labels *Wichtig* und *Information* aussortiert. Der Titel wird dagegen **nie** ausgewertet: CC-Anträge
|
||||
heißen mal „CC Antrag | Name", mal „\"The Duke\" Rang Update" — daran ist nichts zu erkennen.
|
||||
heißen mal „CC Antrag | Name", mal „\"The Duke\" Rang Update" - daran ist nichts zu erkennen.
|
||||
|
||||
Trägst du einen Antrag von Hand ein — über *Neuer CC* oder *Redesign* auf der Spielerseite —
|
||||
Trägst du einen Antrag von Hand ein - über *Neuer CC* oder *Redesign* auf der Spielerseite -
|
||||
kannst du dort den Thread-Link angeben. Der Antrag gilt damit als erledigt und taucht beim
|
||||
nächsten Abgleich nicht erneut auf.
|
||||
|
||||
Ein Thread behält seine ID, auch wenn er zwischen den Boards verschoben wird — daran hängt die
|
||||
Ein Thread behält seine ID, auch wenn er zwischen den Boards verschoben wird - daran hängt die
|
||||
Wiedererkennung. Wandert ein **bereits entschiedener** Antrag später in ein anderes Board, etwa
|
||||
ins Archiv, erscheint das oben als Hinweis. Es ändert nie automatisch den CC-Status: dafür ist
|
||||
das Signal zu unzuverlässig.
|
||||
@@ -212,16 +212,16 @@ das Signal zu unzuverlässig.
|
||||
|
||||
Es gibt **keine öffentliche Registrierung**. Konten legt ausschließlich ein Administrator an.
|
||||
|
||||
- **Viewer** — darf nur lesen.
|
||||
- **Editor** — Spieler anlegen und bearbeiten, gekaufte Pakete eintragen, CCs erstellen,
|
||||
- **Viewer** - darf nur lesen.
|
||||
- **Editor** - Spieler anlegen und bearbeiten, gekaufte Pakete eintragen, CCs erstellen,
|
||||
redesignen und reaktivieren, Datei-Import, GExtension- und Forums-Abgleich samt Entscheidung
|
||||
über Anträge.
|
||||
- **Admin** — zusätzlich Kulanz vergeben, Korrekturbuchungen, unklare Pakete auflösen, Spieler
|
||||
- **Admin** - zusätzlich Kulanz vergeben, Korrekturbuchungen, unklare Pakete auflösen, Spieler
|
||||
löschen, Nutzerverwaltung, Produkt-Mappings und Audit-Log.
|
||||
|
||||
Die Grenze verläuft zwischen *erfassen* und *gewähren*: Wer einen Kauf einträgt, hält fest, was
|
||||
ohnehin passiert ist. Kulanz dagegen ist eine Entscheidung des Teams und bleibt Administratoren
|
||||
vorbehalten — ebenso das Auflösen unklarer Pakete, weil dabei Kulanz vergeben werden kann.
|
||||
vorbehalten - ebenso das Auflösen unklarer Pakete, weil dabei Kulanz vergeben werden kann.
|
||||
|
||||
Jede ändernde Aktion landet im Audit-Log. Für Administratoren lässt sich unter *Mein Konto*
|
||||
eine Zwei-Faktor-Anmeldung per TOTP einrichten.
|
||||
@@ -240,7 +240,7 @@ 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:
|
||||
Die Tests brauchen **keine** laufende Datenbank - sie starten eine Postgres-Instanz im Speicher:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
@@ -273,7 +273,7 @@ Nach Änderungen am Datenmodell:
|
||||
npm run migrate:dev -- --name kurze-beschreibung
|
||||
```
|
||||
|
||||
Der Name wird zum Verzeichnisnamen der Migration — dort also besser bei ASCII bleiben.
|
||||
Der Name wird zum Verzeichnisnamen der Migration - dort also besser bei ASCII bleiben.
|
||||
|
||||
## Sicherheit
|
||||
|
||||
|
||||
Reference in New Issue
Block a user