Leonard Surac

Zur Startseite

Live · seit 2026

Website und Vereinssoftware des Schachklubs Sulzfeld

Mein Schachverein hatte eine veraltete Website und organisierte alles über Chats und private Postfächer. Ich habe ihm eine Website mit Redaktionssystem, Mitgliederbereich und Spieltag-Planung gebaut und betreibe sie.

ASP.NET Core 10 · EF Core · Angular 22 SSR · PostgreSQL · Node.js · Docker · Caddy

Problem

Die Website meines Vereins war veraltet und nur von einer Person zu pflegen. Ligaergebnisse tippte jemand von Hand ab, Rundmails gingen aus privaten Postfächern raus, Aufstellungen wurden in Chatgruppen zusammengesucht. Viel Arbeit für Ehrenamtliche, und nichts davon war für Besucher sichtbar.

Meine Rolle

Ich habe die Lösung allein konzipiert, entwickelt und in Betrieb genommen, und ich betreibe sie bis heute.

Fallstudie

Meine Lösung

Ich wollte, dass die Vereinsarbeit dort passiert, wo die Daten liegen: ein Redaktionssystem für den Vorstand, ein Mitgliederbereich für Einreichungen und ein Import, der alles holt, was andere Systeme ohnehin schon wissen.

  • Ergebnisse, Wertungszahlen und Spiellokale importiere ich jede Nacht aus nuLiga und vom Schachbund
  • Partieformulare werden per Foto zu PGN: eine KI transkribiert, Stockfish prüft, offene Züge entscheidet ein Mensch
  • Mitglieder reichen Artikel, Spielberichte und Rundmails ein, der Vorstand gibt sie frei
  • Die Spieltag-Planung schlägt Aufstellungen vor und sammelt Verfügbarkeiten und Fahrgemeinschaften

Umsetzung

Ich habe die Seite mit Angular und Server-Side-Rendering gebaut, damit sie schnell lädt und gefunden wird, dazu eine ASP.NET-Core-API auf PostgreSQL und einen eigenen Node-Worker für den Import. Weil die Software Mitgliederdaten verarbeitet, habe ich die Rechte von Anfang an verschlossen gestartet: Jeder Endpunkt verlangt einen Admin, Mitglieder- und Gastbereiche öffne ich gezielt.

SkSulzfeld.Api/Program.cs
builder.Services.AddAuthorization(options =>
{
  var adminOnly = new AuthorizationPolicyBuilder()
      .RequireAuthenticatedUser()
      .RequireRole(nameof(UserRole.Admin))
      .Build();
  options.DefaultPolicy = adminOnly;
  options.FallbackPolicy = adminOnly;
  options.AddPolicy(AuthorizationPolicies.MemberArea, policy => policy
      .RequireAuthenticatedUser()
      .RequireRole(nameof(UserRole.Admin), nameof(UserRole.Member)));
});

Betrieb

Ich deploye per GitHub Actions mit einem Datenbank-Dump vor jedem Rollout, teste vorher auf einem Staging in meinem Homelab und sichere jede Nacht verschlüsselt außer Haus. Einmal im Monat spielt ein automatischer Restore-Drill die Sicherung zur Probe ein. Das Repository ist privat, weil es Mitgliederdaten verarbeitet.

Entscheidungen

Alles ist zu, bis ich es öffne
Ich habe mich gegen eine feine Rollenmatrix entschieden: Ohne Freigabe braucht jeder Endpunkt einen Admin, und ein Test friert die öffentliche Oberfläche ein. Ein vergessenes Attribut sperrt aus, statt etwas offenzulegen.
Ich kopiere keine personenbezogenen Daten
Mitgliederdaten schreiben keine Revisionen, und Rundmail-Protokolle verweisen auf das Mitglied, statt die Adresse zu kopieren. So lebt niemand nach dem Austritt im Verlauf weiter; einen Änderungsverlauf gibt es dafür nicht.
Niemand bekommt eine Rundmail doppelt
Ich beanspruche jeden Empfänger vor dem Versand in einer einzigen Datenbank-Anweisung. Zwei parallele Anfragen können so niemanden doppelt anschreiben. Stirbt der Prozess genau dazwischen, bleibt ein Empfänger als versendet stehen.

Ergebnis

Seit September 2026 ist die Seite live. Der Vorstand pflegt Inhalte selbst, Mitglieder können Berichte über den Mitgliederbereich einreichen, Ergebnisse und Wertungszahlen kommen jede Nacht von allein. Dahinter stehen 26 Fachbereiche mit rund 1.400 Backend- und 2.000 Frontend-Tests.