Skip to content

Vereins-Stammdaten-Kanal

Zweck. Der Vereinskatalog des Admin-Portals soll die Vereine zeigen, die eine angeschlossene Box in ihrer Vereinssoftware kennt — auch dann, wenn dort noch niemand eingewilligt hat.

Warum es den Kanal braucht

Der Katalog leitete „importierte" Vereine bisher allein aus ClubDataScoreSheet.vereinsId ab. Dieser Kanal ist am Connector consent-gatet (ConsentGate.java): Ohne GRANTED-Einwilligung verlässt keine Scheibenzeile den Verein. Folge: Eine frisch angeschlossene Box liefert NULL Zeilen und taucht im Katalog gar nicht auf — obwohl sie die Vereine ihres Kreises längst kennt. Genau die Vereine, die man einladen möchte, waren damit unsichtbar.

Am 06.09.2026 lagen im Spiegel von club-413067 107 Vereine in SMDB.Verein (58 davon mit Scheibendaten), während die Prod-Datenbank genau einen kannte.

Das Consent-Gate ist dabei nicht die richtige Stellschraube: SMDB.Verein wird überhaupt nicht übertragen — es gibt kein Verein-Topic, keinen Router, keinen Konsumenten. Ein gelockertes Gate ließe Leistungsdaten ohne Einwilligung in die Cloud und brächte trotzdem keinen einzigen zusätzlichen Verein.

Datenschutz

Übertragen werden ausschließlich Vereinsnummer und Vereinsname — also Organisationsdaten. Wer dort schießt und wie, bleibt vollständig consent-gated in ClubDataScoreSheet / -Serie / -Hit.

Dieselbe Begründung trägt schon den Starterlisten-Stammdaten-Kanal (docs/install/starterlist-master-channel-spec.md): Ein Header ohne Personenbezug braucht keine Einwilligung. Im Löschregister ist AdminAuditEvent.clubDisplayName bereits als „Vereinsname, kein Personenbezug" geführt.

Randfall wie beim Starterlisten-Kanal: Ein Vereinsname, der aus einem einzelnen Personennamen besteht, wäre ein Organisationsdatum mit Personenbezug. Für Schützenvereine ist das praktisch ausgeschlossen; falls es auftritt, gilt dieselbe Empfehlung wie dort (Whitelist oder Hashing).

Löschweg

Anders als der Starterlisten-Kanal hat dieser Kanal von Anfang an einen — das war die Bedingung für seine Einführung.

Der Push ist eine vollständige Meldung des Kenntnisstands, kein Nachtrag:

  • Was gemeldet wird, wird aufgefrischt (lastSeenAt, sourceDeleted = false).
  • Was fehlt, wird im selben Vorgang auf sourceDeleted = true gesetzt.
  • Eine leere Meldung räumt den gesamten Kenntnisstand dieser Box ab. Das ist der Griff, mit dem ein abgebauter Anschluss aus dem Katalog verschwindet.
  • Der Schlüssel ist (reportedByClubId, vereinsId). Zeilen eines einzelnen Anschlusses lassen sich damit gezielt entfernen: DELETE FROM "ClubDataKnownClub" WHERE "reportedByClubId" = '<clubId>';

Der Ersatz läuft in EINER Transaktion mit einem Riegel je meldender Box (pg_advisory_xact_lock, dasselbe Muster wie services/keygenSyncLock.ts). Zwei Gründe:

  1. Ohne Transaktion hinterlässt ein Fehler nach dem dritten von hundert Vereinen einen halb ersetzten Stand.
  2. Ohne Riegel laufen zwei überlappende Meldungen derselben Box in eine Verklemmung: A hält Verein 101 und will 102, B hält 102 und will 101. PostgreSQL erschlägt eine der beiden Transaktionen (40P01), und die betroffene Meldung scheitert mit einem 500, obwohl an ihr nichts falsch war. Nachgestellt in backend/test/clubMasterChannel.db.test.ts.

Scharfschalten

Zwei Schritte, in dieser Reihenfolge:

  1. Backend-Release, das POST /api/edge/clubs/sync kennt. Vorher antwortet die Cloud 404, und der Connector meldet genau das als Grund.
  2. Auf der Box (in .env neben TARGETSHOT_EDGE_API_TOKEN):

TARGETSHOT_CLUB_MASTER_SYNC_ENABLED=true

Per Vorgabe steht der Schalter auf false. Er ist die bewusste Freigabe dafür, dass Stammdaten dritter Vereine das Haus verlassen — diese Entscheidung gehört dem Betreiber, nicht einem Rollout.

Takt: TARGETSHOT_CLUB_MASTER_SYNC_SECONDS (Vorgabe 21600 = 6 h; Stammdaten ändern sich selten). Ein Lauf lässt sich in der Box-Oberfläche über POST /api/club-master-sync/run anstoßen, der Stand steht unter GET /api/club-master-sync/status.

Wirkung im Portal

Ein so gemeldeter Verein erscheint im Vereinskatalog als imported_only — genau wie ein Verein, der über Scheibendaten bekannt wurde. Der Scheiben-Kanal bleibt der stärkere Beleg („hier wurde wirklich geschossen") und wird nicht überschrieben; die Stammdaten-Meldung ergänzt nur, was er nicht sehen kann.

„Aufnehmen" (Bootstrap) findet den Verein in beiden Quellen. Ohne diesen zweiten Zweig stünde ein Verein zwar im Katalog, liefe beim Aufnehmen aber in einen 404 — sichtbar, aber nicht einladbar.