Files
MindSift/Doku/architecture.md
T
sandrodachandClaude Opus 5.5 ce321ce579 fix(smtp): proper SMTP dialogue, encoded mails, optional skip-verify
- Explicit SMTP flow (dial -> EHLO -> STARTTLS -> AUTH -> MAIL/RCPT/DATA)
  with errors naming the refusing step (also logged as smtp_step)
- EHLO with the sender's domain instead of "localhost"
- Envelope gets the bare address; "Name <addr>" stays in the From header
  (was rejected as invalid MAIL FROM)
- Date and Message-ID headers, RFC 2047 subject, quoted-printable body
- SMTP_SKIP_VERIFY / admin switch for self-signed certificates;
  SMTP checkboxes are switches now
- Tests against a fake SMTP server (STARTTLS, 451, display names)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:28:26 +01:00

8.9 KiB

Architektur

Überblick

Browser (React SPA, PWA-Manifest)
   │  REST /api/v1/*           Authorization: Bearer <JWT | mindsift_sec_…>
   │  WebSocket /api/v1/ws     Fallback: SSE /api/v1/events
   ▼
Go-Server (chi)  ── internal/api   Handler, Middleware, Alarm-Ticker
   │                internal/ws    Hub (Räume, Benutzer-Kanäle, SSE)
   │                internal/auth  JWT, bcrypt, API-Key-Hash
   ▼                internal/config
SQLite (modernc.org/sqlite, WAL)  ── internal/storage

Das Frontend liegt per go:embed im Binary (cmd/server/main.go). Unbekannte Pfade liefern index.html (SPA-Routing); index.html, sw.js und manifest.json werden mit no-store ausgeliefert.

Stack

Schicht Technik
Backend Go 1.23, go-chi/chi v5, go-chi/cors, gorilla/websocket, golang-jwt/jwt v5, x/crypto/bcrypt
Datenbank SQLite über modernc.org/sqlite (reines Go, kein CGO), WAL, foreign_keys=ON
Frontend React 18, TypeScript, Vite 5, Tailwind 3, framer-motion (Triage-Karten), lucide-react, prismjs (Code-Blöcke)
Auslieferung Mehrstufiges Dockerfile (Node 22 → Go 1.23 → Alpine 3.21), Image git.dsit.me/sandrodach/mindsift

Verzeichnisse

Pfad Inhalt
cmd/server/ main.go, eingebettetes dist/
internal/api/ server.go (Routen, Handler, Alarm-Ticker, SMTP/Discord/Webhook-Versand)
internal/storage/ sqlite.go (Schema, Migrationen, gesamte Datenlogik)
internal/domain/ Strukturen (models.go), ID-Erzeugung, Tag-Extraktion
internal/ws/ hub.go
internal/collab/ Kern der Notiz-Synchronisation ohne Datenbank: Blockbaum, Operationen und Konfliktregeln, Sortierschlüssel, Leases, Long-Poll-Benachrichtigung
internal/auth/ Tokens und Passwörter
internal/config/ Umgebungsvariablen
frontend/src/features/ triage, workspace, lists, teams, settings, capture, auth, help
frontend/src/lib/ api.ts (REST-Client, Transport-Umschaltung WS/SSE), types.ts, markdown.tsx, theme.ts
frontend/src/lib/docSync/ Notiz-Synchronisation im Browser: Portierung von internal/collab (model.ts, orderKey.ts), Vergleich Editor-Liste → Operationen (diff.ts), engine.ts, IndexedDB (persist.ts), HTTP (transport.ts)
e2e/ Browser-Tests mit Playwright gegen einen echten Server (e2e/run.sh)
frontend/src/themes/ CSS pro Theme

Start und Laufzeit

  1. config.Load() liest die Umgebung (siehe unten).
  2. storage.NewStore() öffnet SQLite (busy_timeout, WAL, max. 8 Verbindungen) und führt migrate() aus.
  3. SMTP-Werte aus der Umgebung werden in system_settings übernommen, sofern dort noch nichts steht.
  4. Mit Demo-Flag legt EnsureDemoAccounts die Konten admin/admin und user/user an.
  5. api.NewServer startet den Alarm-Ticker (jede Minute), die Verdichtung des Notiz-Logs (beim Start, dann alle 6 Stunden) und baut den Router. Server-Timeouts: Lesen 15 s, Schreiben 30 s, Leerlauf 60 s. Beim Beenden wird sauber heruntergefahren.

Konfiguration

Variable Standard Bedeutung
PORT 8080 HTTP-Port
DATA_DIR ./data (Container: /data) Verzeichnis der Datenbank
DATABASE_URL leer sqlite:///pfad/datei.db oder Pfad; ohne Angabe DATA_DIR/mindsift.db. Postgres-URLs werden nicht unterstützt (siehe Hinweis)
JWT_SECRET festes Fallback (unsicher!) Signaturschlüssel der Tokens; unbedingt setzen, siehe FLAW-04
BASE_URL http://localhost:<PORT> Basis-URL der Instanz
ALLOW_REGISTRATION false Startwert; die Einstellung im Admin-UI (system_settings) hat Vorrang
LOG_LEVEL / DEBUG info / false debug, info, warn, error; DEBUG=true erzwingt debug
MINDA_DEMO (auch MINDSIFT_DEMO, DEMO_MODE) false Demo-Konten anlegen
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM, SMTP_TLS leer / 587 / true Mailversand, auch im Admin-UI einstellbar
SMTP_SKIP_VERIFY false Zertifikat des Mailservers nicht prüfen (nur interne Server mit selbstsigniertem Zertifikat)

Hinweis: DATABASE_URL mit postgres… führt dazu, dass der Standardpfad in DATA_DIR genutzt wird; Postgres ist nicht implementiert.

Authentifizierung

  • Login mit E-Mail, Benutzername (aus dem E-Mail-Präfix) oder login. Passwörter: bcrypt (DefaultCost).
  • JWT: HS256, Laufzeit 30 Tage, Claims userId, email, displayName, tokenVersion, jti. Gültig nur, wenn Token nicht in revoked_tokens steht, das Konto aktiv ist und tokenVersion mit users.token_version übereinstimmt. token_version wird bei Passwortänderung, Rollenwechsel, Deaktivierung und „Alle Sitzungen beenden“ erhöht.
  • Logout schreibt den Token-Hash in revoked_tokens.
  • API-Keys: mindsift_sec_<hex>, gespeichert als SHA-256, nur das Präfix bleibt sichtbar. Der Key gilt als Bearer-Token oder in X-API-Key und authentifiziert als Besitzer (FLAW-07).
  • Registrierung: nur mit Einladungscode oder wenn allow_registration aktiv ist. Das erste Konto entsteht über /auth/setup und wird Administrator.

Hintergrundjob: Alarme

startAlarmTicker prüft jede Minute alle offenen, noch nicht gemeldeten Aufgaben mit Fälligkeit. Ist due_date <= heute (Serverzeit), sendet er über die Kanäle des Benutzers (email, discord, webhook) und setzt alarm_sent, sobald mindestens ein Kanal erfolgreich war. Details und Einschränkungen: modules/tasks.md, FLAW-12.

Hintergrundjob: Verdichtung der Notiz-Synchronisation

startCompaction räumt beim Start und dann alle 6 Stunden auf: abgelaufene Leases und Sitzungen im Speicher, Log-Einträge älter als 30 Tage (private Notizen: 7 Tage), Grabsteine älter als 30 Tage und Idempotenz-Ergebnisse älter als 90 Tage. Details: database.md.

Build

Stage 1  node:22-alpine   tsc, vitest, vite build            → frontend/dist
Stage 2  golang:1.23      go vet, go test, go build          → /app/mindsift (dist wird eingebettet)
Stage 3  alpine:3.21      nur Binary, ca-certificates, tzdata

Image-Tags (CI): Push auf dev → :dev; Push/Merge auf main → :latest; Git-Tag v* (z. B. v1.2.0) → :v1.2.0 und :stable. Andere Branches bauen nicht.

Die angezeigte Version (Kopf der App) setzt der Build-Parameter APP_VERSION: Die CI übergibt bei Git-Tags den Tag-Namen (v0.2), sonst zeigt die App die Version aus frontend/package.json mit -dev (v0.2-dev). Umgesetzt in frontend/vite.config.ts und frontend/src/version.ts.

Die Tests laufen beim Image-Build (Frontend: npm run test:run, Backend: go test -v ./...); ein Fehler bricht den Build ab. Frontend-Tests liegen unter frontend/src/**/__tests__/ (Vitest + jsdom + Testing Library, Setup in frontend/src/test/setup.ts).

App-Icons, PWA & Social-Preview (Open Graph)

MindSift verwendet plattformübergreifend ein einheitliches App-Icon-Design, das exakt dem Icon im Kopfbereich der mobilen App entspricht:

  • Design & Geometrie: Sanft abgerundetes Quadrat (rx=108 auf 512x512 Canvas bzw. rounded-xl), Primärblau (--accent-primary: #2563eb) als Hintergrund und zentriertes Lucide Brain Icon in reinem Weiß (--text-on-accent: #ffffff).

Asset-Matrix (frontend/public/)

Datei Zweck & Zielplattform Besonderheit
icon.svg Modernes Standard-Icon & SVG-Favicon Vektor-Original mit abgerundeten Ecken
icon-192x192.png, icon-512x512.png PWA-Installation (purpose: "any"), Desktop Linux/Windows Erforderlich für Chromium unter Linux (erzeugt sonst kein .desktop-Icon)
icon-maskable-192x192.png, icon-maskable-512x512.png, icon-maskable.svg Android Adaptive Icons (purpose: "maskable") Full-Bleed Hintergrund (#2563eb) ohne transparente Ecken, Brain im 80%-Sicherheitsbereich
apple-touch-icon.png (180x180) iOS Safari („Zum Home-Bildschirm“) Opaker Hintergrund verhindert schwarze Ränder beim automatischen Apple-Squircle-Maskieren
favicon.ico, favicon-32x32.png, favicon-16x16.png Browser-Tabs, Lesezeichen, Chronik Multi-Resolution-ICO (16/32/48) für alle Desktop-Browser
og-image.png (1200x630), og-image-square.png (512x512) Link-Vorschau in WhatsApp, Discord, Telegram, X/Twitter Dunkler #0b0f19-Hintergrund mit Icon & Claim; Datei-Größe < 120 KB (WhatsApp-Limit < 300 KB)

Dynamische OpenGraph-URL-Injektion

Messenger-Crawler (insbesondere WhatsApp) werten relative Bild-URLs in og:image oft nicht korrekt aus. Der Go-SPA-Handler in internal/api/server.go ersetzt beim Ausliefern von index.html automatisch relative Pfade (/og-image.png, og:url) durch die absolute Server-Origin (scheme://host), ermittelt über X-Forwarded-Proto und X-Forwarded-Host bzw. req.Host.