尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Interne Web-App für die Strafen- und Mannschaftskasse unseres Fußballteams. Zwei Kassenwarte verwalten mit einem gemeinsamen Passwort Strafen, Kisten-Strafen, Zahlungen und den Kassenbestand. Läuft auf Smartphone, Tablet und PC, komplett auf Deutsch.
Ihr braucht dafür nur einen Browser, kein Terminal und kein Node.js. Alles passiert in Vercel; die Datenbank (Supabase) wird direkt aus Vercel heraus angelegt.
Was beim Deployen automatisch passiert: Beim Build auf Vercel (Produktion) richtet die App die Datenbank selbst ein bzw. aktualisiert sie: Tabellen, Sicherheitsregeln (RLS), Löschschutz, die 21 Spieler und der Strafenkatalog. Es werden keine Zahlungen, Strafen oder Kassenstände erfunden. Der Vorgang ist wiederholbar und verändert vorhandene Daten nicht.
Euer Repo ist bereits mit Vercel verbunden (Adresse kasse-jade.vercel.app). Falls nicht: bei vercel.com Add New → Project, das Repo Wang20923/Kasse importieren und Deploy klicken. Das erste Deployment klappt auch ohne Datenbank; die App zeigt dann auf der Login-Seite an, was noch fehlt.
Vercel trägt die Verbindungsdaten (POSTGRES_URL, POSTGRES_URL_NON_POOLING und weitere) automatisch als Umgebungsvariablen ein. Ihr müsst nichts kopieren.
Ihr habt schon ein eigenes Supabase-Projekt? Dann statt der Integration unter Settings → Environment Variables die Variable
DATABASE_URLmit der „Transaction pooler“-Verbindung aus Supabase (Button Connect) anlegen.
KASSE_PASSWORD, Wert = euer gemeinsames Passwort (mindestens 12 Zeichen, am besten ein kurzer Satz).Das Passwort steht damit weder im Code noch in der Datenbank. Der Server leitet daraus beim Start einen scrypt-Schlüssel ab und prüft jeden Login dagegen.
Deployments → beim obersten Eintrag auf ⋯ → Redeploy. Neue Umgebungsvariablen werden nur bei einem neuen Deployment übernommen.
Im Build-Log seht ihr dann z. B.:
[Datenbank] Verbunden mit aws-0-eu-central-1.pooler.supabase.com:5432
[Datenbank] ✔ angewendet: 0001_schema.sql
[Datenbank] ✔ angewendet: 0002_seed.sql
https://kasse-jade.vercel.app öffnen (bzw. eure eigene Domain), mit dem Passwort anmelden, optional den eigenen Namen angeben.strafenkasse-export-….json gespeichert. (Ein PDF oder Screenshot der alten Seite reicht nicht: Darin stehen keine Daten, nur die Oberfläche bzw. der Programmcode.)So wird übernommen:
| Alte Strafenkasse | Neue App |
|---|---|
| Geld-Eintrag, offen | Geldstrafe mit Betrag, Datum und Kommentar |
| Geld-Eintrag, bezahlt | Geldstrafe plus Zahlungseingang zum damaligen Bezahldatum |
| Bier-Eintrag (Kiste) | Kisten-Strafe; bei 2 Kisten zwei Einträge, bezahlte als „erledigt“ |
| Papierkorb | wird nicht übernommen |
| Strafarten der alten App | werden nicht in den Katalog übernommen, der Name bleibt aber bei jeder Strafe erhalten |
Der Import läuft in einer Transaktion (ganz oder gar nicht), wird im Verlauf protokolliert und kann gefahrlos wiederholt werden: Bereits übernommene Einträge werden erkannt und nicht doppelt angelegt.
kasse.euer-verein.de hinzufügen und den angezeigten DNS-Eintrag beim Domain-Anbieter setzen. HTTPS kommt automatisch.db/migrations/0002_seed.sql. Auf GitHub unter Settings → General → Danger Zone → Change visibility auf „Private“ stellen; Vercel funktioniert damit genauso.claude/vibrant-heisenberg-2dblke). Wer lieber main hat, benennt den Branch auf GitHub um und stellt in Vercel unter Settings → Git → Production Branch denselben Namen ein. Vorschau-Deployments aus anderen Branches verändern die Datenbank nicht.| Bereich | Was geht |
|---|---|
| Dashboard | Kassenbestand, offene Schulden, offene und überfällige Strafen, Einnahmen Monat und Jahr, offene Kisten, Rangliste der höchsten Schulden, letzte Buchungen, großer Button „Strafe eintragen“ |
| Spieler | 21 Spieler vorangelegt, Namen und Kennzeichnung bearbeitbar, hinzufügen, deaktivieren (Daten bleiben), Profil mit Schulden, Zusatzgebühren, Zahlungen, Kisten und kompletter Historie |
| Strafe eintragen | Schnellerfassung fürs Handy: Datum und Anlass bleiben stehen, mehrere Spieler auf einmal, automatische Berechnung, Zusammenfassung vor dem Speichern, Warnung bei möglichen Doppelbuchungen, mehrere Strafen nacheinander ohne Neustart |
| Offene Schulden | Filter (offen, überfällig, bald fällig, bezahlt), Suche, Sortierung nach Name, Restschuld und Fälligkeit, Summenzeile, direkter Sprung ins Profil, Button „Schulden kopieren“ für WhatsApp (eine Zeile pro Spieler, z. B. Paul – 5,00 €, alphabetisch, aktuelle Restschuld inkl. manueller Zusatzgebühren und Teilzahlungen, ohne Kisten) |
| Kasse und Zahlungen | Tatsächlicher Kassenbestand, Zahlungen erfassen (automatisch verteilt oder gezielt pro Strafe, Teilzahlungen), Vorschau der Verteilung, Kassenkorrekturen, Einnahmen pro Monat und pro Spieler, Storno mit Begründung |
| Kisten-Strafen | Eigene Übersicht, offen/erledigt/storniert, als erledigt markieren mit Datum und Notiz |
| Strafenkatalog | Strafen hinzufügen, bearbeiten, aktivieren, deaktivieren, Geld oder Kiste, Berechnungsart, Auswärts-Faktor, Bindung an Training oder Spiel, Doppelbuchungsschutz |
| Verlauf | Lückenloses, unveränderbares Protokoll mit Zeitpunkt und Admin-Name, filterbar |
| Einstellungen | Zahlungsfrist, „Bald fällig“-Schwelle, Anfangsbestand, Verspätungsregel, angemeldete Geräte, Backup und Wiederherstellung, Import aus der alten Strafenkasse |
Alle Geldbeträge werden als ganze Cent gespeichert und gerechnet, nie als Gleitkommazahl. Alle Kalenderdaten beziehen sich auf Europe/Berlin.
aufgerundet(Minuten / 2) × 1 €. 1 oder 2 Min. = 1 €, 3 oder 4 Min. = 2 €, 5 Min. = 3 €, 10 Min. = 5 €. 0 Minuten erzeugen keine Strafe. Betrag und Intervall sind in den Einstellungen bzw. im Katalog änderbar.Jede gespeicherte Strafe friert Bezeichnung, Einzelbetrag, Anzahl, Minuten, Faktor, und Fälligkeit ein. Spätere Änderungen am Katalog oder an den Einstellungen ändern bestehende Strafen nicht.
src/lib/domain/balance.ts, getestet in tests/unit/balance.test.ts).Zusatzgebühren entstehen ausschließlich, wenn ein angemeldeter Administrator sie einträgt:
Eine Zusatzgebühr ist ein eigener Posten mit dem Kennzeichen „manuell“: Sie erhöht die offene Geldschuld, erscheint in der Schuldenübersicht (Spalte Zusatzgebühren), im Text von „Schulden kopieren“, in der Kasse unter „Offene Geldforderungen“ und „Zusatzgebühren“ und kann wie eine Strafe ganz oder in Teilen bezahlt, bearbeitet oder mit Begründung storniert werden. Wer sie wann für wen mit welchem Grund eingetragen hat, steht im Spielerprofil und im Verlauf (Filter „Zusatzgebühren“).
Frühere Versionen haben nach Fristablauf automatisch 0,50 € pro Woche berechnet. Diese Gebühren waren nie eigene Buchungen, sondern wurden aus einem Gebührensatz pro Strafe abgeleitet. Die Migration 0003_manual_surcharges.sql setzt diesen Satz bei allen Strafen und in den Einstellungen auf 0 und protokolliert die Umstellung im Verlauf (Eintrag „System“). Danach gilt:
Auf Vercel läuft die Migration beim nächsten Produktions-Deploy automatisch.
Kassenbestand = Anfangsbestand + bestätigte (nicht stornierte) Zahlungen + Kassenkorrekturen
Offene Schulden zählen nicht als Einnahmen. Kisten-Strafen sind keine Geldbuchungen. Ausgaben (z. B. Getränke) werden als negative Kassenkorrektur mit Begründung erfasst.
Jeder Spieler hat eine eigene ID, alle Buchungen hängen an der ID, nicht am Namen. Zwei Spieler mit gleichem Namen sind erlaubt, brauchen aber eine unterschiedliche Kennzeichnung (z. B. Nachname, Rückennummer). Die App zeigt dann z. B. „Moritz (Torwart)“. Ohne Kennzeichnung lässt sich ein zweiter „Moritz“ nicht anlegen (das prüft auch die Datenbank).
Strafen, Zahlungen, Kisten und Korrekturen werden nie gelöscht, sondern mit Begründung storniert. Die Datenbank verhindert Löschen per Trigger. Zahlungen und Korrekturen können nach dem Speichern nicht verändert werden (nur storniert und neu erfasst). Strafen können bearbeitet werden (Datum, Fälligkeit, Betrag, Kommentar), jede Änderung wird mit Vorher/Nachher und Grund protokolliert. Das Protokoll selbst ist unveränderbar.
pg (node-postgres) mit parametrisierten Abfragenvercel.json)db/migrations/ SQL-Migrationen (Schema, RLS, Trigger, Startdaten)
scripts/ migrate, hash-password, backup/restore
src/lib/domain/ Reine Rechenlogik: Geld, Datum, Strafen, Restschuld, Verteilung, Kasse
src/lib/validation.ts Gemeinsame Eingabeprüfung (Client und Server)
src/server/auth/ Passwort-Hash, Sessions, Rate-Limit, Login
src/server/services/ Buchungslogik mit Transaktionen und Protokoll
src/app/actions/ Server-Aktionen (prüfen immer die Session)
src/app/(app)/ Seiten der App
src/components/ UI-Bausteine und Fach-Komponenten
src/proxy.ts Erste Schutzschicht (Next.js Proxy, früher „Middleware“)
tests/ Unit- und Integrationstests
KASSE_PASSWORD bei Vercel (verschlüsselt, als „Sensitive“ nicht mehr auslesbar), nie im Code und nie in der Datenbank. Der Server vergleicht über einen scrypt-abgeleiteten Schlüssel in konstanter Zeit. Wer das Passwort gar nicht bei Vercel hinterlegen will, kann stattdessen nur dessen scrypt-Hash als KASSE_PASSWORD_HASH setzen (npm run hash-password); der Hash hat Vorrang. Optional gibt jeder beim Login seinen Namen an, der im Verlauf erscheint.__Host-kasse_session (HttpOnly, Secure, SameSite=Lax, 30 Tage). In der Datenbank liegt nur der SHA-256-Hash des Tokens. Sessions lassen sich in den Einstellungen für andere Geräte beenden. Ändert ihr das Passwort, werden alle bestehenden Sessions automatisch ungültig.anon und authenticated; deren Rechte werden zusätzlich entzogen. Über die Supabase-API (anon key) ist also nichts lesbar oder schreibbar. Die App selbst verbindet sich serverseitig mit den Datenbank-Zugangsdaten, die nur in Vercel hinterlegt sind. Die von der Supabase-Integration zusätzlich angelegten Schlüssel (z. B. NEXT_PUBLIC_SUPABASE_*) verwendet die App nicht.X-Frame-Options: DENY, frame-ancestors 'none', nosniff, Referrer-Policy, Permissions-Policy, X-Robots-Tag: noindex..env* ist in .gitignore, nur .env.example ohne Werte ist eingecheckt.| Variable | Pflicht | Beschreibung |
|---|---|---|
POSTGRES_URL | ja* | Wird von der Supabase-Integration in Vercel automatisch gesetzt (gepoolte Verbindung, für die App) |
POSTGRES_URL_NON_POOLING | nein | Ebenfalls automatisch gesetzt; wird bevorzugt für Migrationen genutzt. Ist sie vom Build aus nicht erreichbar, nimmt die App automatisch die gepoolte Verbindung |
DATABASE_URL | ja* | Alternative zu POSTGRES_URL für ein eigenes Supabase-Projekt (Transaction pooler, Port 6543). Hat Vorrang |
MIGRATION_DATABASE_URL | nein | Eigene Verbindung nur für Migrationen und Backups (Session pooler, Port 5432) |
KASSE_PASSWORD | ja** | Das gemeinsame Passwort, mind. 12 Zeichen, in Vercel als „Sensitive“ |
KASSE_PASSWORD_HASH | ja** | Alternative: nur der scrypt-Hash des Passworts (npm run hash-password). Hat Vorrang |
DATABASE_SSL_CA | nein | Inhalt des Supabase-CA-Zertifikats für vollständige TLS-Prüfung (sonst verschlüsselt ohne Zertifikatsprüfung) |
DATABASE_POOL_MAX | nein | Max. Datenbankverbindungen pro Serverinstanz, Standard 3 |
KASSE_MIGRATE_PREVIEW | nein | Auf 1 setzen, wenn auch Preview-Deployments Migrationen ausführen sollen (nur mit eigener Test-Datenbank sinnvoll) |
* eine der beiden. ** eine der beiden.
db/migrations/ und werden alphabetisch angewendet (0001_..., 0002_...).npm run build führt zuerst scripts/deploy-migrate.ts aus. Das läuft nur bei Produktions-Deployments auf Vercel. Schlägt die Migration fehl, wird das Deployment abgebrochen, damit nie eine App mit falschem Datenbankschema live geht; die bisherige Version bleibt dann online.npm run db:migrate.schema_migrations, jede Migration läuft in einer eigenen Transaktion.enable row level security (ein Test prüft das) und die revoke-Anweisungen für anon und authenticated.KASSE_PASSWORD bearbeiten (bei „Sensitive“: löschen und neu anlegen).Regelmäßig sichern! Verlasst euch im kostenlosen Supabase-Tarif nicht auf automatische Backups.
WIEDERHERSTELLEN eintippen. Das ersetzt alle aktuellen Daten durch den Stand der Sicherung; man bleibt angemeldet, und die Wiederherstellung wird im Verlauf protokolliert. Geht etwas schief, wird nichts verändert (eine Transaktion).npm run db:backup bzw. npm run db:backup -- restore <datei> --confirm, oder klassisch pg_dump.Nur nötig, wenn ihr am Code arbeiten wollt. Voraussetzung: Node.js 22.
git clone https://github.com/Wang20923/Kasse.git && cd Kasse
npm install
cp .env.example .env.local # DATABASE_URL und KASSE_PASSWORD eintragen
npm run db:migrate
npm run dev # http://localhost:3000
npm run dev # Entwicklungsserver
npm test # Unit- und Integrationstests (ohne externe Datenbank)
npm run lint # ESLint
npm run typecheck # TypeScript
npm run build # Produktions-Build
npm run check # alles zusammen
Abgedeckt sind unter anderem: Verspätung 0 bis 11 Minuten, unabgemeldetes Fehlen bei Training und Spiel, mehrere gelbe Karten, Verdopplung beim Auswärtsspiel, Fristablauf und alle Grenzfälle um das Fälligkeitsdatum, keine automatischen Gebühren nach Fristablauf, manuelle Zusatzgebühren mit Bestätigung, Duplikatschutz und Protokoll, Umstellung bestehender Daten ohne Änderung von Strafen und Zahlungen, Teil- und Vollzahlung, parallele Buchungen, Doppelbuchungsschutz, Storno, Trennung von offenen Schulden und Einnahmen, Kassenbestand, Kisten ohne Geldwirkung, getrennte Verwaltung gleichnamiger Spieler (Moritz, Willem), Katalog- und Einstellungsänderungen ohne Rückwirkung, RLS auf allen Tabellen, Löschschutz, Login, Rate-Limit, Session-Ablauf und Zugriffsschutz für nicht angemeldete Nutzer.
GitHub Actions führt Lint, Typecheck, Tests und Build bei jedem Push aus (.github/workflows/ci.yml).
| Problem | Lösung |
|---|---|
| Login-Seite zeigt „Einrichtung noch nicht abgeschlossen“ | Die angezeigten Schritte abarbeiten (Datenbank verbinden, KASSE_PASSWORD setzen) und danach Redeploy |
| „Datenbank einrichten“ wird angezeigt | Die Datenbank ist verbunden, aber leer: Redeploy des Produktions-Deployments, dabei werden die Tabellen angelegt |
| „Datenbank nicht erreichbar“ | Im Supabase-Dashboard prüfen, ob das Projekt pausiert ist (kostenlose Projekte pausieren nach längerer Inaktivität), dann fortsetzen |
| Deployment bricht mit „[Datenbank] Migration fehlgeschlagen“ ab | Verbindung prüfen (Supabase-Projekt aktiv? Integration mit „Production“ verbunden?), dann erneut deployen. Die vorherige Version bleibt solange online |
| Passwort geändert, aber das alte gilt noch | Nach dem Ändern der Variable ist ein Redeploy nötig |
| „Zu viele Fehlversuche“ | 15 Minuten warten; die Sperre gilt pro Gerät/IP |