VoteBroker Tech — 31. Mai 2026
VoteBroker — Technischer Rückblick 31. Mai 2026
⚠ Entwurf — Vor der Veröffentlichung manuell überprüfen.
Keine Secrets, Keys, Infrastruktur-Details oder private Daten veröffentlichen.
Geänderte Bereiche
- Root cause: In-memory state lost after container restart
- Umsetzung: SQLite Persistence Layer
- Hydration Race-Condition Fix
- Smoke Test — Vollständiger Persistenz-Test
- Ergebnis
- Vote-DNA Tab: Erste Überarbeitung (Analyse → Onboarding-Tool)
- Bugfix: Vote-DNA API nicht erreichbar (Failed to fetch)
- Vote-DNA Strategy Editor: Editierbare Strategie-Tabelle
Architektur-Entscheidungen
Keine Architektur-Entscheidungen dokumentiert.
Root Causes & Fixes
Root cause: In-memory state lost after container restart
Problem
sessionStore.ts und consentStore.ts verwendeten Map<> im Prozessspeicher.
Bei jedem Container-Neustart wurden alle Sessions und Consents gelöscht:
- Nutzer wurden automatisch ausgeloggt
- Vote-Consent musste nach jedem Restart erneut erteilt werden
- Strategie-Regeln existierten nur im Browser (localStorage)
- Posting-Authority wurde bei jedem Vote erneut vom Steem-Node abgefragt
Symptome für den Nutzer: Generischer "network error" beim Voten nach Server-Neustart.
Hydration Race-Condition Fix
Problem: localStorage-Daten konnten API-Daten überschreiben wenn der Debounce-Timer feuerte bevor die API-Antwort ankam.
Lösung: strategyHydrated-Flag. Die Debounce-Persistenz startet erst nachdem getPersistedStrategy() abgeschlossen ist. Reihenfolge garantiert:
Login
→ getPersistedStrategy() [wartet]
→ setStrategyRules(apiData)
→ setStrategyHydrated(true)
→ ab jetzt: Änderungen → debounce → persistStrategy()
Bugfix: Vote-DNA API nicht erreichbar (Failed to fetch)
Problem
api.ts hatte const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:3000". Die .env-Datei mit VITE_API_BASE=/api lag im Monorepo-Root /opt/votebroker-modern/.env, aber Vite liest .env nur aus dem Workspace-Verzeichnis apps/web/. Damit fiel der Build auf http://localhost:3000 zurück – vom Browser aus nicht erreichbar. Der Fehler wurde zusätzlich durch
Tests & Verifikation
Smoke Test — Vollständiger Persistenz-Test
Datum: 2026-05-31
Durchgeführt: Programmatisch via API-Calls und SQLite-Direktabfragen
Testablauf
Vorbedingung: Clean-Slate für smoketest-user in allen Tabellen.
Schritt 1 — Login:
- Session direkt in SQLite erstellt (simuliert SteemConnect OAuth)
GET /api/auth/me→ 200 OK,{"username":"smoketest-user"}
Schritt 2 — Alle Consents erteilt:
POST /api/consents/grantfürlogin,target_vote,fee_post_vote,auto_vote- Alle 4 in
consents-Tabelle persistiert
Schritt 3+4 — Strategy generiert + manuell überschrieben:
- 5 Regeln: steemchiller (lieblingsautor), gtg (bevorzugt), kingscrown (normal), jan-philippvieth (immer_voten, MANUAL), spammer99 (ignorieren)
POST /api/strategy→{"ok":true,"savedRules":5}manuallyModified: truefür @jan-philippvieth gesetzt
Schritt 5 — Pre-Restart-Verifikation:
- DB-Inhalt bestätigt: 1 Session, 4 Consents, 5 Strategy-Regeln
**Schritt 6 — Co
| Session State | ✓ PASS — Session überlebt Neustart |
| Consent State | ✓ PASS — Alle 4 Consents erhalten |
| Strategy State | ✓ PASS — Alle 5 Regeln erhalten |
| Manual Override | ✓ PASS — manuallyModified für @jan-philippvieth erhalten |
4/4 Tests bestanden. Persistence-Milestone abgeschlossen.
Technische Details
- Nutzer wurden automatisch ausgeloggt
- Vote-Consent musste nach jedem Restart erneut erteilt werden
- Strategie-Regeln existierten nur im Browser (localStorage)
- Posting-Authority wurde bei jedem Vote erneut vom Steem-Node abgefragt
getDb()— Singleton-Verbindung, lazy-initialized- WAL-Modus für bessere Concurrent-Reads
initSchema()— erstellt Tabellen beim ersten StartpruneExpiredSessions()— bereinigt abgelaufene Sessions beim Start- DB-Pfad konfigurierbar via
VOTEBROKER_DB_PATH(Standard:./data/votebroker.db) - ESM-safe via
createRequirefürbetter-sqlite3(CJS-natives Modul) - SQLite statt in-memory
Map - API identisch zu vorher:
createSession / getSession / deleteSession - SQLite statt in-memory
Map - API identisch:
grantConsent / revokeConsent / hasConsent / getConsentState loadStrategy(username)— lädt JSON-Array aus DB
Nächste technische Schritte
fetchRecentPostsWithVotes(author, voter, limit)— ruftget_discussions_by_blogvom Steem-Node ab- Filtert Reblogs (nur eigene Posts des Autors)
- Prüft
active_votesauf Voter-Username →alreadyVoted - Berechnet Eligibility: nicht bereits gevoted, min. 5 Min. alt, max. 6.5 Tage alt (vor Payout-Lock)
- Gibt
PostOpportunity[]zurück POST /api/curation/opportunities— nimmt{ authors: string[], voterUsername: string }
Stack-Überblick
- Runtime: Node.js 20 (Alpine Docker)
- API: Fastify + TypeScript (ESM/NodeNext)
- Persistence: SQLite via better-sqlite3 (WAL mode)
- Frontend: React 18 + Vite + TypeScript
- Deployment: Docker + Caddy reverse proxy
- Auth: SteemConnect OAuth (in-house session management)
💬 Engagement für Tech-Leser:
- Interessieren dich technische Deep-Dives zu SQLite, ESM-Interop oder Fastify-Architektur?
- Hast du Feedback zur gewählten Persistence-Strategie (SQLite vs. Redis/Postgres)?
- Welche Architektur-Entscheidungen möchtest du in einem eigenen Post erklärt haben?
- Fragen oder Kritik zum Stack? Gerne in die Kommentare.
Dieser Beitrag ist ein technischer Einblick in die VoteBroker-Entwicklung.