diff --git a/Doku/README.md b/Doku/README.md index ef02845..4e6f1cf 100644 --- a/Doku/README.md +++ b/Doku/README.md @@ -1,134 +1,40 @@ -# 📚 MindSift Technische Dokumentation & Wiki +# MindSift Technische Dokumentation -Willkommen in der zentralen technischen Dokumentation von **MindSift**. Dieses Wiki dient als verbindliche, tiefgehende Referenz fĂŒr Entwickler, DevOps-Ingenieure und Systemadministratoren. Es dokumentiert die Architektur, das Datenmodell, APIs, Echtzeit-Protokolle, Kernmodule sowie bekannte technische Schwachstellen der Plattform. +Referenz fĂŒr Entwickler und Administratoren. Alle Angaben wurden gegen den Code im Branch `dev` geprĂŒft (Stand Oktober 2026). Abweichungen zwischen Doku und Code melde bitte als Fehler in [flaws.md](flaws.md). ---- +## Inhalt -## 🧭 Inhaltsverzeichnis (Wiki-Navigation) +| Dokument | Inhalt | +|---|---| +| [architecture.md](architecture.md) | Aufbau, Technologie-Stack, Start, Konfiguration, Build | +| [database.md](database.md) | SQLite-Schema, Migrationen, Datenmodell | +| [api/rest.md](api/rest.md) | Alle REST-Endpunkte | +| [api/websocket.md](api/websocket.md) | Echtzeit ĂŒber WebSocket und SSE, Ereignisse | +| [modules/triage.md](modules/triage.md) | Schnellerfassung und Triage | +| [modules/tasks.md](modules/tasks.md) | Aufgaben, Kanban, Alarme | +| [modules/workspace.md](modules/workspace.md) | Outliner-Notizen, Ordner, Tags | +| [modules/lists.md](modules/lists.md) | Geteilte Listen | +| [sharing-and-permissions.md](sharing-and-permissions.md) | Rollen, Gruppen, Freigaben | +| [reverse-proxy.md](reverse-proxy.md) | Nginx, Traefik, Caddy, Cloudflare | +| [theming.md](theming.md) | Themes und Design-Variablen | +| [flaws.md](flaws.md) | Bekannte Schwachstellen und technische Schulden | -``` -Doku/ -├── README.md # Einstieg, Inhaltsverzeichnis & GesamtĂŒbersicht (dieses Dokument) -├── architecture.md # Systemarchitektur, Schichtenmodell, Concurrency & Stack -├── database.md # SQLite-Datenbankdesign, Schemas, Indizes & Migrationen -├── api/ -│ ├── rest.md # VollstĂ€ndige REST-API Referenz aller Endpunkte -│ └── websocket.md # WebSocket-Echtzeitprotokoll, Rooms & Document Locking -├── modules/ -│ ├── triage.md # Modul 1: Triage & Quick-Dump (State Machine & Audio) -│ ├── tasks.md # Modul 2: Aufgaben & Kanban-System (Alarme & Scheduler) -│ ├── workspace.md # Modul 3: Workspace Outliner-Engine, Wiki-Links & Pure-DB Storage -│ └── lists.md # Modul 4: Geteilte Listen (Live-Kollaboration & D&D) -├── sharing-and-permissions.md # Synchrones Teilen, Rollen, Isolation & Mount-Entkopplung -├── reverse-proxy.md # Nginx, Traefik, Caddy, Cloudflare Konfiguration & SSE -├── theming.md # Single-File CSS Theme-Engine & Design-System -└── flaws.md # Technische Schwachstellen, Risiken & zukĂŒnftige Optimierungen -``` +## Überblick in 60 Sekunden -### 📑 Kapitel im Detail -1. **[Systemarchitektur & Technologie-Stack](architecture.md)** - Schichtenmodell, Go-Backend, React-Frontend, Concurrency-Handling und Thread-Safety. -2. **[Datenbank-Design & SQLite Schema](database.md)** - VollstĂ€ndiges relationales Datenmodell, ER-Diagramm, Tabellendefinitionen, Indizes und Migrationsstrategie. -3. **[REST-API Referenz](api/rest.md)** - Spezifikation aller Endpunkte fĂŒr Authentifizierung, Gedanken, Aufgaben, Dokumente, Listen, Teams und Webhooks. -4. **[WebSocket & Echtzeit-Synchronisation](api/websocket.md)** - Gorilla WebSocket Hub, Rooms, Presence-Tracking, Yjs CRDT Kollaboration und SSE Fallback. -5. **[Kernmodul: Triage & Quick-Dump](modules/triage.md)** - Instant-Capture-Pipeline, Finite State Machine, Gestensteuerung, Spracheingabe und Webhook-Ingestion. -6. **[Kernmodul: Aufgaben & Kanban-System](modules/tasks.md)** - Multi-Listen-Konzept, 3-Spalten Kanban-Board, Subtasks, FĂ€lligkeiten und Multi-Channel Alarm-Ticker (SMTP, Discord, Webhooks). -7. **[Kernmodul: Workspace & Outliner-Engine](modules/workspace.md)** - Logseq-inspirierte hierarchische Block-Engine, Zoom-Hoisting, Wiki-Links, Pure-Database Storage und Globales To-Do Rollup. -8. **[Kernmodul: Geteilte Listen & Kollaboration](modules/lists.md)** - Listen-Typen, Mengen, Kategorien, Live-Checkoff und Drag & Drop Sortierung. -9. **[Synchrones Teilen & Berechtigungskonzept](sharing-and-permissions.md)** - Rollenmodell (Admin/Member), System-Isolations-Modi (`strict_private`, `teams_only`, `open`), entkoppelte Ordner-Mounts und Notizen-Freigaben. -10. **[Reverse-Proxy- & Ingress-Handbuch](reverse-proxy.md)** - Produktionsfertige Konfigurationen fĂŒr Nginx, Traefik, Caddy, Cloudflare und Apache mit WebSocket-Upgrades und SSE-Streaming. -11. **[Single-File Theming & Design-System](theming.md)** - 1-Datei = 1-Theme Philosophie, die 6 CSS-Variablen-Blöcke, Semantische CSS-Klassen und Geometrie-Steuerung. -12. **[Technische Schwachstellen & Optimierungspotenziale (Flaws)](flaws.md)** - Kritische Analyse von Concurrency-Grenzen, Volltextsuche, Viewport-Anpassung und Skalierungs-EngpĂ€ssen. +- **Ein Go-Binary** liefert REST-API, WebSocket/SSE und das eingebettete React-Frontend (`go:embed`). Es gibt genau einen Container und eine SQLite-Datei (`/data/mindsift.db`). +- **Datenhaltung:** vollstĂ€ndig in SQLite. Es gibt keine `.md`-Dateien auf der Platte; Markdown wird bei Bedarf aus den Blöcken erzeugt (`documents.markdown_cache`, Export als `.md`). +- **Echtzeit:** ein Hub mit RĂ€umen und Benutzer-KanĂ€len. Verteilt werden Ereignisse nach REST-Änderungen. Es gibt keine CRDTs, Sperren oder Cursor-Presence. +- **Auth:** JWT (HS256, 30 Tage) mit `token_version` und Sperrliste, dazu API-Keys (`mindsift_sec_
`) fĂŒr Webhooks. +- **Freigaben:** Aufgabenlisten und geteilte Listen mit Rolle Bearbeiter oder Leser, Notizen und Ordner nur lesend. ---- +## Entwickeln -## đŸ›ïž SystemĂŒbersicht (High-Level Architektur) - -```mermaid -graph TB - subgraph Client ["Client Layer (Browser / PWA)"] - UI["React 18 SPA (Vite, TS, Tailwind)"] - WSClient["WebSocket Client (Auto-Reconnect)"] - Cache["IndexedDB / Offline Queue"] - end - - subgraph ReverseProxy ["Reverse Proxy / Gateway"] - Traefik["Traefik / Nginx (HTTPS, WSS, Rate Limits)"] - end - - subgraph Backend ["Backend Layer (Go 1.23 Engine)"] - ChiRouter["Chi Router (REST API)"] - WSHub["Gorilla WebSocket Hub & Lock Manager"] - AlarmTicker["Alarm Scheduler (Background Ticker)"] - StorageEngine["Storage Layer (Domain & Repositories)"] - end - - subgraph Storage ["Storage Layer"] - SQLite[("SQLite WAL Database (/data/mindsift.db)")] - end - - subgraph External ["Externe Schnittstellen"] - HomeAssistant["Home Assistant / Webhooks"] - Discord["Discord Webhooks"] - SMTPServer["SMTP Server (E-Mail Alarme)"] - end - - UI -->|HTTPS REST| Traefik - WSClient -->|WSS Realtime| Traefik - Traefik --> ChiRouter - Traefik --> WSHub - ChiRouter --> StorageEngine - StorageEngine --> SQLite - AlarmTicker --> Discord - AlarmTicker --> SMTPServer - HomeAssistant -->|POST /api/v1/inbox| Traefik - HomeAssistant -->|POST /api/v1/webhooks/ingest| Traefik -``` - ---- - -## đŸ› ïž Schnellstart fĂŒr Entwickler - -### Voraussetzungen -- **Go:** 1.22 oder 1.23+ -- **Node.js:** 20+ (LTS) & npm -- **Container-Runtime:** Docker oder Podman - -### Lokale Entwicklung ohne Container ```bash -# 1. Repository klonen -git clone https://git.dsit.me/sandrodach/MindSift.git -cd MindSift +# Backend (Go 1.23) +go vet ./... && go test ./... -# 2. Frontend AbhĂ€ngigkeiten installieren & Dev-Server starten -cd frontend -npm install -npm run dev - -# 3. Backend starten (in neuem Terminal) -cd .. -go run ./cmd/server +# Frontend (Node 22) +cd frontend && npm ci && npx tsc --noEmit && npm run build ``` -### Container Build & Testlauf (wie in der CI/CD) -```bash -# Backend-Tests ausfĂŒhren -podman run --rm -v $(pwd):/app:z -w /app golang:1.23 go test -v ./internal/... - -# Frontend Typecheck & Build testen -podman run --rm -v $(pwd):/app:z -w /app/frontend node:20 sh -c "npx tsc --noEmit && npm run build" - -# Komplettes Multi-Stage Image bauen -podman build -t mindsift:dev . -``` +Das Frontend wird fĂŒr das Binary nach `cmd/server/dist` gebaut (siehe `Dockerfile`). Die CI lĂ€uft unter `.gitea/workflows/build-and-publish.yaml`. diff --git a/Doku/api/rest.md b/Doku/api/rest.md index 14d0845..e14f675 100644 --- a/Doku/api/rest.md +++ b/Doku/api/rest.md @@ -1,185 +1,129 @@ -# 🔌 REST-API Referenz +# REST-API -Die MindSift REST-API ermöglicht den vollstĂ€ndigen Zugriff auf Gedanken, Aufgaben, Workspace-Notizen, Listen, Benutzerkonten und Smarthome-Automationen. +Basis: `/api/v1`. Antworten sind JSON; Fehler haben die Form `{"error": "Text"}`. Authentifizierung ĂŒber `Authorization: Bearer ` oder `Bearer mindsift_sec_
` bzw. Header `X-API-Key`. Zeitstempel sind ISO-8601 (UTC). Quelle der Wahrheit sind die Routen in [server.go](../../internal/api/server.go). ---- +## Öffentlich -## 🔐 1. Authentifizierung +| Methode | Pfad | Zweck | +|---|---|---| +| GET | `/health` | Lebenszeichen | +| GET | `/auth/status` | `needsSetup`, `allowRegistration`, `instanceName`, `demoMode` | +| POST | `/auth/setup` | Erstes Administratorkonto (`email`, `password`, `displayName`, `instanceName`, `allowRegistration`, `sharingMode`, `seedSampleData`); nur solange es keine Konten gibt | +| POST | `/auth/register` | Registrierung (`email`, `password`, `displayName`, `inviteCode`); nur mit Code oder wenn freigegeben | +| POST | `/auth/login` | Anmeldung (`login`/`email`/`username`, `password`) → `{token, user}` | +| POST | `/auth/quickstart` | Immer `403` (Gastzugang deaktiviert) | +| POST | `/webhooks/ingest` | Aufgabe per Webhook anlegen, Token als `?token=`, `X-API-Key` oder Bearer. Body: `title`/`content`/`text`/`message`, `priority`, `source`, `tags`, `dueDate`; Klartext-Body wird als Inhalt genommen | +| GET | `/ws`, `/ws/spaces/{id}`, `/ws/lists/{id}` | WebSocket, siehe [websocket.md](websocket.md) | +| GET | `/events` | SSE-Fallback | +| POST | `/events/rooms` | SSE-RĂ€ume beitreten/verlassen ([FLAW-02](../flaws.md)) | -MindSift unterstĂŒtzt zwei Authentifizierungsmethoden: +## Konto und Allgemeines (angemeldet) -1. **JSON Web Token (JWT Bearer):** - Wird beim Login oder Setup erzeugt und im HTTP-Header mitgesendet: - `Authorization: Bearer ` -2. **Persönliche API-SchlĂŒssel (Smarthome / Automatisierung):** - Werden unter *Einstellungen ➔ Private Webhooks* erzeugt (PrĂ€fix `mindsift_sec_...`): - - Per HTTP-Header: `X-API-Key: mindsift_sec_xxxxxxxxxxxx` - - Per Authorization-Header: `Authorization: Bearer mindsift_sec_xxxxxxxxxxxx` - - Per Query-Parameter: `?token=mindsift_sec_xxxxxxxxxxxx` +| Methode | Pfad | Zweck | +|---|---|---| +| GET | `/auth/me` | Benutzer und Kennzahlen | +| POST | `/auth/logout` | Token sperren | +| POST | `/auth/revoke-all` | Alle Sitzungen beenden (`token_version` +1) | +| PATCH | `/auth/settings` | `displayName`, `email`, `password`, `avatarUrl`, `alarmChannels`, `alarmEmail`, `discordWebhookUrl`, `customWebhookUrl`, `notifyBeforeMinutes` | +| POST | `/auth/alarms/test` | Test-Alarm (`channel`: `email`/`discord`/`webhook`, optional `target`) | +| GET | `/users/directory`, `/collaborators` | Benutzerverzeichnis fĂŒr Freigaben ([FLAW-10](../flaws.md)) | +| GET | `/stats` | ZĂ€hler fĂŒrs Dashboard | +| GET | `/search?q=` | Suche in eigenen Gedanken und Notizen | +| GET | `/export` | Datenexport als JSON | +| GET/POST/DELETE | `/keys`, `/keys/{id}` | API-Keys verwalten (Klartext nur bei Erstellung) | ---- +## Gedanken, Triage, Aufgaben -## 🚩 2. Status- & Fehler-Codes +| Methode | Pfad | Zweck | +|---|---|---| +| POST | `/inbox` | Schnellerfassung. Body: `content`, `priority`, `source`, `dueDate`, `target` (`inbox`/`task`/`list_item`/`document`), `targetListId`, `taskListId` (snake_case-Varianten werden akzeptiert) | +| GET | `/thoughts?status=` | Gedanken (`inbox`, `kept`, `converted`, `discarded`, `all`) | +| GET/PATCH/DELETE | `/thoughts/{id}` | Einzelabruf; TeilĂ€nderung (`content`, `isCompleted`, `dueDate`, `priority`, `taskListId`, `kanbanStatus`, `notes`, `subtasks`, `tags`); Löschen | +| DELETE | `/thoughts/discarded/purge` | Papierkorb leeren | +| GET | `/triage/deck` | Posteingang (max. 100) | +| PATCH | `/triage/{id}` | `action`: `keep`/`discard`/`restore`; mit `convertTo`: `task`/`document`/`list_item` | +| GET | `/tasks?taskListId=` | Aufgaben (optional einer Liste) | +| POST | `/tasks/reorder`, `/task-lists/{id}/tasks/reorder` | Reihenfolge/Spalte Ă€ndern (`items[{id, sortOrder, kanbanStatus}]`) | +| POST | `/tasks/{id}/unlink` | VerknĂŒpfung zu Notiz-Block lösen | -- `200 OK`: Erfolgreiche Abfrage oder Aktualisierung. -- `201 Created`: Ressource erfolgreich angelegt. -- `400 Bad Request`: UngĂŒltiges JSON oder fehlende Pflichtfelder. -- `401 Unauthorized`: Token ungĂŒltig, abgelaufen oder fehlend. -- `403 Forbidden`: Unzureichende Berechtigungen (z.B. nur Server-Admin oder nur Lesezugriff). -- `404 Not Found`: Ressource existiert nicht oder gehört einem anderen Benutzer. -- `409 Conflict`: Ressource gesperrt (z.B. Dokument wird bereits von anderem Benutzer bearbeitet). +## Aufgabenlisten -FehlerrĂŒckgaben erfolgen im einheitlichen Format: -```json -{ - "error": "Fehlermeldung im Klartext" -} +| Methode | Pfad | Zweck | +|---|---|---| +| GET/POST | `/task-lists` | Listen (`permission` je Liste: `owner`/`editor`/`viewer`); anlegen (`title`, `icon`, `color`, `teamId`) | +| PUT/DELETE | `/task-lists/{id}` | Ändern (`title`, `icon`, `color`, `teamId`, `columns`) – nicht fĂŒr Leser; Löschen nur EigentĂŒmer | +| GET | `/task-lists/{id}/shares` | Freigaben | +| POST | `/task-lists/{id}/shares` | Nur EigentĂŒmer. Body: `targetType` (`team`/`user`), `targetId`, `permission` (`editor`/`viewer`, Standard `editor`). Erneutes Senden fĂŒr dasselbe Ziel Ă€ndert die Stufe | +| DELETE | `/task-lists/{id}/shares/{shareId}` | Nur EigentĂŒmer | + +Schreibzugriffe von Lesern auf Listen oder deren Aufgaben liefern `403` („Keine Schreibrechte fĂŒr diese Aufgabenliste (nur Lesen)“). + +## Workspace + +| Methode | Pfad | Zweck | +|---|---|---| +| GET/POST/DELETE | `/folders` | Ordnerliste, anlegen (`path`), löschen | +| POST | `/folders/move` | `sourcePath`, `targetParentPath` | +| GET/POST | `/workspace/folders/shared` | Geteilte Ordner; anlegen (`name`, `mountPath`) | +| GET/DELETE | `/workspace/folders/shared/{id}` | Abruf; Löschen | +| PUT | `/workspace/folders/shared/{id}/mount` | Eigenen EinhĂ€ngepfad Ă€ndern (`mountPath`) | +| GET/POST/DELETE | `/workspace/folders/shared/{id}/shares[/{shareId}]` | Freigaben (nur Lesen wirksam) | +| GET | `/workspace/tags`, `/workspace/tags/{tag}` | Tag-Übersicht und -Details | +| PUT | `/workspace/tags/{tag}/note` | Tag-Seite speichern (`content`) | +| GET/POST | `/documents` | Notizen auflisten; anlegen (`title`, `icon`, `folderPath`, `blocks`, `tags`, `isPinned`) | +| GET/PUT/DELETE | `/documents/{id}` | Abruf; Ändern (`title`, `icon`, `folderPath`, `blocks`, `tags`, `isPinned`); Löschen | +| PATCH | `/documents/{id}/blocks` | Teil-Update der Blöcke (`upserted`, `deletedIds`, Reihenfolge); löst `DOC_BLOCKS_PATCHED` aus | +| PUT | `/documents/{id}/move` | Ordner wechseln (`folderPath`) | +| POST | `/documents/{id}/blocks/{blockId}/enhanced-task` | Aus einem Block eine verknĂŒpfte Aufgabe machen (`content`, `checked`) | +| GET/POST/DELETE | `/documents/{id}/shares[/{userId}]` | Notiz-Freigaben (`userId`/`email`/`teamId`; wirksam nur Lesen) | + +Nur der EigentĂŒmer schreibt in eine Notiz; EmpfĂ€nger erhalten `403`/„nur Lesezugriff“ ([FLAW-17](../flaws.md)). + +## Geteilte Listen + +| Methode | Pfad | Zweck | +|---|---|---| +| GET/POST | `/lists` | Listen; anlegen (`title`, `icon`, `listType`) | +| GET/DELETE | `/lists/{id}` | Abruf mit EintrĂ€gen; Löschen | +| GET/POST/DELETE | `/lists/{id}/shares[/{shareId}]` | Freigaben (`targetType`, `targetId`, `role`: `editor`/`viewer`) | +| POST | `/lists/{id}/items` | Eintrag (`content`, `category`, `quantity`) | +| PATCH | `/lists/{id}/items/{itemId}` | Abhaken (`isCompleted`) | +| PUT | `/lists/{id}/items/{itemId}` | Ändern (`content`, `category`, `quantity`) | +| DELETE | `/lists/{id}/items/{itemId}` | Löschen | +| POST/PUT | `/lists/{id}/items/reorder` | Reihenfolge/Kategorie (`items[{id, sortOrder, category, content}]`) | +| POST | `/lists/{id}/clear-completed` | Erledigte entfernen | + +## Gruppen + +| Methode | Pfad | Zweck | +|---|---|---| +| GET/POST | `/teams` | Eigene Gruppen; anlegen (`name`, `description`) – Ersteller wird Gruppen-Admin | +| PUT/DELETE | `/teams/{id}` | Nur Gruppen-Admin | +| POST | `/teams/{id}/members` | Mitglied hinzufĂŒgen (`userId` oder `email`, `role`) | +| PUT/DELETE | `/teams/{id}/members/{userId}` | Rolle Ă€ndern (`admin`/`member`); entfernen | +| GET/POST/DELETE | `/organizations`, `/organizations/{id}[/teams]` | Veraltet, ungeschĂŒtzt ([FLAW-24](../flaws.md)) | + +## Administration (`role = admin`) + +| Methode | Pfad | Zweck | +|---|---|---| +| GET/POST | `/admin/users` | Benutzer auflisten, anlegen (`email`, `password`, `displayName`, `role`) | +| PATCH/DELETE | `/admin/users/{id}` | `displayName`, `email`, `role`, `isActive`, `discordWebhookUrl`; löschen | +| PUT | `/admin/users/{id}/password` | Passwort zurĂŒcksetzen | +| GET/POST/DELETE | `/admin/invites[/{id}]` | Einladungen (`email`, `role`, `teamId`, `validDays`) | +| GET/PUT | `/admin/settings` | `instanceName`, `allowRegistration`, `sharingMode`, SMTP-Felder | +| POST | `/admin/smtp/test` | Testmail (`toEmail`) | + +## Beispiele + +```bash +# Gedanken per API-Key erfassen +curl -X POST https://mindsift.example.com/api/v1/webhooks/ingest \ + -H "X-API-Key: mindsift_sec_
" -H "Content-Type: application/json" \ + -d '{"title":"Milch kaufen #einkauf","priority":"high","dueDate":"2026-11-01"}' + +# Aufgabenliste nur lesend freigeben +curl -X POST https://mindsift.example.com/api/v1/task-lists/tkl_
/shares \ + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ + -d '{"targetType":"user","targetId":"usr_
","permission":"viewer"}' ``` - ---- - -## 📋 3. Endpunkte nach Modulen - -### 3.1 Authentifizierung & System-Status - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `GET` | `/api/v1/health` | Öffentlich | Liveness-Check (`{"status":"ok"}`) | -| `GET` | `/api/v1/auth/status` | Öffentlich | PrĂŒft ob Ersteinrichtung nötig ist (`needsSetup: true/false`) | -| `POST` | `/api/v1/auth/setup` | Öffentlich | FĂŒhrt First-Boot Setup durch (legt ersten Admin an) | -| `POST` | `/api/v1/auth/register` | Öffentlich | Registrierung ĂŒber Einladungscode (`inviteCode`) | -| `POST` | `/api/v1/auth/login` | Öffentlich | Login via E-Mail oder Benutzername + Passwort | -| `GET` | `/api/v1/auth/me` | JWT | Gibt das aktuelle Benutzerprofil zurĂŒck | -| `PATCH` | `/api/v1/auth/settings` | JWT | Aktualisiert Profil, Passwort, Theme oder Alarm-KanĂ€le | -| `POST` | `/api/v1/auth/alarms/test` | JWT | Sendet Test-Alarm an E-Mail, Discord oder Custom Webhook | - -#### Beispiel: `POST /api/v1/auth/login` -```json -// Request -{ - "login": "sandro", - "password": "geheimesPasswort123" -} - -// Response (200 OK) -{ - "token": "eyJhbGciOiJIUzI1NiIs...", - "user": { - "id": "usr_abc123", - "email": "sandro@example.com", - "username": "sandro", - "displayName": "Sandro", - "role": "admin", - "alarmChannels": ["discord", "webhook"] - } -} -``` - ---- - -### 3.2 Triage & Instant Capture - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `POST` | `/api/v1/inbox` | JWT / API-Key | Erfasst einen neuen Quick Dump oder leitet direkt weiter | -| `GET` | `/api/v1/triage/deck` | JWT | Liefert den Stapel aller noch nicht triagierten Gedanken (`status: 'inbox'`) | -| `PATCH` | `/api/v1/triage/{id}` | JWT | FĂŒhrt Triage-Aktion aus (`keep`, `discard`, `restore`) | - -#### Beispiel: `POST /api/v1/inbox` -Ermöglicht flexible Direkt-Zuordnungen: -```json -// Request (Quick Dump in Triage-Deck) -{ - "content": "Kellerfeuchtigkeit ĂŒber 68% gestiegen #smarthome #alarm", - "priority": "urgent" -} - -// Request (Direkt als Aufgabe anlegen) -{ - "content": "Server Backup prĂŒfen #devops", - "priority": "high", - "target": "task", - "dueDate": "2026-10-30" -} -``` -*Hinweis:* Wenn `/api/v1/inbox` ĂŒber einen API-Key aufgerufen wird und kein `source` definiert ist, wird die Quelle automatisch als `"webhook"` markiert! - ---- - -### 3.3 Aufgaben & Kanban-System - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `GET` | `/api/v1/tasks` | JWT | Liefert alle Aufgaben des Benutzers (inkl. geteilter Aufgaben) | -| `POST` | `/api/v1/tasks/reorder` | JWT | Speichert Kanban-Status und Reihenfolge (`sort_order`) | -| `GET` | `/api/v1/task-lists` | JWT | Liefert alle Aufgabenlisten (eigene & geteilte) | -| `POST` | `/api/v1/task-lists` | JWT | Erstellt eine neue Aufgabenliste (mit Farbe & Spalten) | -| `PUT` | `/api/v1/task-lists/{id}` | JWT | Ändert Titel, Farbe, Icon oder Kanban-Spalten | -| `DELETE` | `/api/v1/task-lists/{id}` | JWT | Löscht Aufgabenliste | -| `POST` | `/api/v1/task-lists/{id}/shares` | JWT | Teilt Liste mit Team oder Benutzer | - ---- - -### 3.4 Workspace & Dokumente - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `GET` | `/api/v1/documents` | JWT | Liefert Liste aller Dokumente des Benutzers (Metadaten) | -| `POST` | `/api/v1/documents` | JWT | Erstellt neues Notiz-Dokument (mit Blocks oder Markdown) | -| `GET` | `/api/v1/documents/{id}` | JWT | LĂ€dt komplettes Dokument inklusive aller Blöcke | -| `PUT` | `/api/v1/documents/{id}` | JWT | Aktualisiert Dokumenttitel, Icon oder Ordnerpfad | -| `PATCH` | `/api/v1/documents/{id}/blocks` | JWT | Granulares Patching von Dokument-Blöcken (Database-First) | -| `DELETE` | `/api/v1/documents/{id}` | JWT | Löscht Dokument (Datenbank & physische Datei) | -| `POST` | `/api/v1/documents/{id}/shares` | JWT | Notiz mit anderem Benutzer teilen (`read` / `write`) | -| `GET` | `/api/v1/documents/{id}/lock` | JWT | Fragt Bearbeitungs-Sperrstatus ab | -| `POST` | `/api/v1/documents/{id}/lock` | JWT | Sperrt Dokument fĂŒr Bearbeitung (30s Lease) | -| `DELETE` | `/api/v1/documents/{id}/lock` | JWT | Gibt Bearbeitungssperre wieder frei | - ---- - -### 3.5 Workspace Ordner & Geteilte Ordner - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `GET` | `/api/v1/folders` | JWT | Liefert alle Ordnerpfade des Benutzers | -| `POST` | `/api/v1/folders` | JWT | Legt neuen (auch leeren) Ordner an | -| `POST` | `/api/v1/folders/move` | JWT | Benennt Ordner um oder verschiebt ihn hierarchisch | -| `DELETE` | `/api/v1/folders` | JWT | Löscht Ordner und verschiebt Notizen | -| `GET` | `/api/v1/workspace/folders/shared` | JWT | Liefert fĂŒr den Benutzer freigegebene geteilte Ordner | -| `POST` | `/api/v1/workspace/folders/shared` | JWT | Erstellt neuen geteilten Ordner | -| `PUT` | `/api/v1/workspace/folders/shared/{id}/mount` | JWT | Ändert den lokalen EinhĂ€ngepfad beim EmpfĂ€nger | -| `POST` | `/api/v1/workspace/folders/shared/{id}/shares` | JWT | Gibt Ordner fĂŒr Teams oder Benutzer frei | - ---- - -### 3.6 Geteilte Listen (Listen & Einkauf) - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `GET` | `/api/v1/lists` | JWT | Liefert alle geteilten Listen | -| `POST` | `/api/v1/lists` | JWT | Erstellt neue geteilte Liste | -| `GET` | `/api/v1/lists/{id}` | JWT | LĂ€dt Liste inklusive aller EintrĂ€ge | -| `POST` | `/api/v1/lists/{id}/items` | JWT | FĂŒgt Eintrag hinzu | -| `PATCH` | `/api/v1/lists/{id}/items/{itemId}` | JWT | Hakt Eintrag ab (`isCompleted: true/false`) | -| `POST` | `/api/v1/lists/{id}/items/reorder` | JWT | Speichert sortierte EintrĂ€ge | -| `POST` | `/api/v1/lists/{id}/clear-completed`| JWT | Löscht alle abgehakten EintrĂ€ge | -| `POST` | `/api/v1/lists/{id}/shares` | JWT | Teilt Liste mit Team oder Benutzer | - ---- - -### 3.7 Smarthome Ingestion & Externe Webhooks - -| Methode | Pfad | Auth | Beschreibung | -| :--- | :--- | :--- | :--- | -| `POST` | `/api/v1/webhooks/ingest` | Token / Key | EmpfĂ€ngt externe Webhook-Payloads von Home Assistant / n8n | - -#### Beispiel: `POST /api/v1/webhooks/ingest?token=mindsift_sec_...` -UnterstĂŒtzt flexible Payloads (JSON oder Plain Text): -```json -{ - "title": "Waschmaschine Keller fertig", - "priority": "high", - "tags": ["smarthome", "keller"] -} -``` -*Live-Aktualisierung:* Sendet automatisch ein `TASK_CREATED` WebSocket-Event an alle Clients des Benutzers, sodass die neue Karte sofort ohne Neuladen im Triage-Deck erscheint! diff --git a/Doku/api/websocket.md b/Doku/api/websocket.md index fb211e6..c2fe7e7 100644 --- a/Doku/api/websocket.md +++ b/Doku/api/websocket.md @@ -1,195 +1,55 @@ -# 📡 WebSocket & Echtzeit-Synchronisation +# Echtzeit: WebSocket und SSE -MindSift implementiert eine bidirektionale Echtzeit-Synchronisationsschicht auf Basis von WebSockets ([`internal/ws/hub.go`](../../internal/ws/hub.go)). Sie sorgt dafĂŒr, dass Änderungen an Aufgaben, Triage-Karten, Notizen und geteilten Einkaufslisten ohne manuelles Neuladen sofort auf allen geöffneten GerĂ€ten und bei allen Kollaborationspartnern sichtbar werden. +Der Server verteilt Änderungen als JSON-Ereignisse an verbundene Clients. Die Ereignisse entstehen **nach** einer REST-Änderung; es gibt keinen Schreibkanal ĂŒber die Verbindung. Implementierung: [hub.go](../../internal/ws/hub.go), Client: [api.ts](../../frontend/src/lib/api.ts). ---- +> Keine CRDTs, keine Sperren, keine Cursor-Presence. FrĂŒhere Fassungen dieser Doku beschrieben DocRooms, `clientId`-Echo-Schutz und Yjs; das existiert nicht (mehr). -## 🔌 1. Verbindung & Handshake +## Transporte -### Verbindungs-Endpunkte -``` -wss:///api/v1/ws?token= -wss:///api/v1/ws/lists/?token= -``` +| Transport | Endpunkt | Hinweise | +|---|---|---| +| WebSocket | `GET /api/v1/ws` (auch `/ws/lists/{id}`, `/ws/spaces/{id}`) | Ping alle 30 s, Lesefrist 60 s, Nachrichten bis 4 MB | +| SSE | `GET /api/v1/events` | Kommentar-Ping alle 15 s, Header `X-Accel-Buffering: no` | -### Authentifizierung & Upgrade -- Die Authentifizierung erfolgt ĂŒber das JWT-Token im Query-Parameter `?token=...` oder den `Authorization: Bearer`-Header wĂ€hrend des HTTP-Upgrade-Requests. -- Der Server extrahiert die `userID` und registriert die Verbindung im internen `Hub`. -- Der `Upgrader` akzeptiert alle Origins (`CheckOrigin: return true`), um volle KompatibilitĂ€t mit Reverse-Proxies (Traefik, Nginx, Caddy, Cloudflare) und Docker-Netzwerken zu gewĂ€hrleisten. +Der Client versucht zuerst WebSocket. Gelingt der Aufbau nicht innerhalb von 2,5 s oder bricht die Verbindung unsauber ab, wechselt er auf SSE und bleibt dabei (Firmen-Proxys). Dort liefert der Server `SSE_CONNECTED` mit einer `sseId`; RĂ€ume werden ĂŒber `POST /api/v1/events/rooms` (`sseId`, `room`, `action`: `join`/`leave`) abonniert. ---- +**Authentifizierung:** Token als `?token=` oder `Authorization`-Header. Ohne gĂŒltiges Token wird die Verbindung als `anonymous` akzeptiert ([FLAW-01](../flaws.md), [FLAW-02](../flaws.md)). Das Token wird nur beim Verbinden geprĂŒft. -## đŸ›ïž 2. Hub-Architektur & Rooms - -Der WebSocket-Hub unterteilt Verbindungen in drei Abstraktionsebenen: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ WebSocket Hub │ -â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”Źâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”Źâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ -│ allClients │ userRooms │ rooms │ -│ (Alle aktiven │ (Pro Benutzer-ID) │ (Pro Liste/ │ -│ Verbindungen) │ Cross-Device Sync fĂŒr │ Space) │ -│ │ Tasks, Triage & Workspace │ Geteilte Liste │ -└────────┬────────┮──────────────┬──────────────┮────────┬────────┘ - â–Œ â–Œ â–Œ - Presence Stats TASK_CREATED ITEM_TOGGLED - DOC_LOCKED TASK_UPDATED ITEM_CREATED - DOC_UNLOCKED DOC_UPDATED LIST_UPDATED -``` - -1. **`allClients` (Global):** HĂ€lt jede aktive Client-Verbindung. Ermöglicht Broadcasts systemweiter Ereignisse (z.B. globale Dokument-Sperrungen). -2. **`userRooms[userID]` (Benutzer-RĂ€ume):** Sendet Events an alle GerĂ€te desselben Benutzers (z.B. Desktop und Smartphone). Erzeugt ein Webhook oder Quick Dump auf dem Smartphone einen Eintrag, taucht dieser zeitgleich auf dem Desktop auf. -3. **`rooms[listID]` (Kollaborations-RĂ€ume):** Multiplexing fĂŒr geteilte Listen. Alle Mitglieder einer Liste empfangen Aktionen (wie Abhaken, Löschen, Umsortieren) in Echtzeit. - ---- - -## 📹 3. Nachrichten-Format (Envelope) - -Alle WebSocket-Nachrichten werden als JSON mit einheitlicher HĂŒlle ĂŒbertragen: +## Nachrichtenformat ```json -{ - "event": "ITEM_TOGGLED", - "listId": "lst_12345", - "payload": { - "itemId": "itm_987", - "isCompleted": true, - "completedBy": "usr_sandro", - "completedByName": "Sandro" - }, - "timestamp": "2026-10-26T13:30:00Z" -} +{ "event": "TASK_UPDATED", "listId": "", "payload": { 
 }, "timestamp": "2026-10-27T12:00:00Z" } ``` ---- +Client → Server versteht der Server nur: `{"event":"JOIN_ROOM","room":""}` und `{"event":"LEAVE_ROOM","room":""}`. RĂ€ume heißen `doc:` fĂŒr Notizen oder `` fĂŒr geteilte Listen. ---- +## Zustellung -## ⚡ 4. Granulare Workspace-Echtzeitsynchronisation (`/api/v1/ws/docs/{id}`) +| Kanal | Wer erhĂ€lt es | +|---|---| +| Benutzer-Kanal (`BroadcastToUser`) | alle Verbindungen eines Benutzers | +| Raum (`BroadcastRoom`, `Broadcast`) | Verbindungen im Raum | +| Alle (`BroadcastToAll`) | **jede** Verbindung, auch unangemeldete | -MindSift nutzt fĂŒr die Outliner-Synchronisation eine schlanke, hochgradig reaktive In-Memory-Raumarchitektur (`DocRoom` in [`internal/ws/docroom.go`](../../internal/ws/docroom.go)). Clients verbinden sich mit ihrer eindeutigen `clientId`: +## Ereignisse -``` -wss:///api/v1/ws/docs/?token=&clientId=&userName=&userColor= -``` - -### 4.1 JSON-Echtzeit-Eventprotokoll -Die Kommunikation erfolgt ĂŒber leichtgewichtige JSON-Nachrichten ohne binĂ€ren CRDT-Overhead: - -| Event-Typ | Richtung | Zweck / Payload | +| Ereignis | Auslöser | Kanal | |---|---|---| -| `doc_state` | Server ➔ Client | Wird direkt beim Verbinden gesendet: Liefert den aktuellen In-Memory-Zustand (`title`, `blocks[]`, `activeCursors[]`). | -| `block_updated` | Bidirektional | Feingranulare Inhalts- oder StatusĂ€nderung eines Blocks (`blockId`, `content`, `level`, `type`, `checked`, `collapsed`). | -| `block_created` | Bidirektional | Erzeugt einen neuen Block (`block`, `afterBlockId`, `newIndex`). | -| `block_deleted` | Bidirektional | Löscht einen Block anhand seiner `blockId`. | -| `block_moved` | Bidirektional | Verschiebt einen Block / Unterbaum (`blockId`, `newIndex`, `level`). | -| `title_updated` | Bidirektional | Aktualisiert den Dokumententitel (`title`). | -| `cursor_moved` | Bidirektional | **Ephemere Live-PrĂ€senz & Cursor:** Sendet Benutzer-Avatar, Cursorposition und Textselektion (`clientId`, `userId`, `userName`, `userColor`, `blockId`, `selectionStart`, `selectionEnd`). Wird nicht in SQLite gespeichert! | -| `client_left` | Server ➔ Client | Wird automatisch ausgelöst, wenn ein Peer die Verbindung trennt, damit Remote-Cursor sofort verschwinden. | +| `TASK_CREATED` | `POST /inbox` | Benutzer | +| `TASK_UPDATED` | `PATCH /thoughts/{id}` (Triage: Benutzer) | alle | +| `TASK_DELETED` | `DELETE /thoughts/{id}` | alle | +| `TASK_REORDERED` | Aufgaben umsortieren | alle | +| `TASK_LIST_UPDATED` / `TASK_LIST_DELETED` | Liste Ă€ndern, (un)teilen, löschen | alle | +| `DOC_CREATED`, `DOC_UPDATED`, `DOC_DELETED` | Notiz anlegen/Ă€ndern/löschen, Freigaben | berechtigte Benutzer | +| `DOC_BLOCKS_PATCHED` | `PATCH /documents/{id}/blocks` (`docId`, `updatedBy`, `upserted`, `deletedIds`, `blockOrder`) | Raum `doc:` und berechtigte Benutzer | +| `WORKSPACE_FOLDERS_UPDATED` | Ordner/Mounts Ă€ndern | Benutzer | +| `LIST_CREATED`, `LIST_UPDATED`, `LIST_DELETED`, `LIST_SHARED`, `LIST_UNSHARED`, `LIST_CLEARED` | Listenverwaltung | Benutzer bzw. Raum der Liste | +| `ITEM_CREATED`, `ITEM_UPDATED`, `ITEM_TOGGLED`, `ITEM_DELETED`, `ITEM_REORDERED` | ListeneintrĂ€ge | Raum der Liste | +| `USER_PRESENCE` | Verbindung auf-/abgebaut (`onlineCount`) | Raum | +| `SSE_CONNECTED` | Nur SSE, direkt nach Verbindung | Verbindung | -```mermaid -sequenceDiagram - autonumber - actor UserA as Admin (Fenster A, cli_1) - participant Room as DocRoom (Go RAM) - actor UserB as User (Fenster B, cli_2) - participant DB as SQLite (blocks, block_tags, documents) - - UserA->>Room: Connect (clientId="cli_1", userName="Admin") - Room-->>UserA: doc_state (title, blocks, activeCursors) - UserB->>Room: Connect (clientId="cli_2", userName="User") - Room-->>UserB: doc_state (title, blocks, activeCursors) - - UserA->>Room: block_updated (clientId="cli_1", blockId="b1", content="Neu") - Note over Room: Mutiert RAM-Slice & resetet 1.5s Timer - Room-->>UserB: block_updated (clientId="cli_1", blockId="b1", content="Neu") - Note over UserA: Echo Filter: cli_1 erhĂ€lt kein Selbstecho! - - UserB->>Room: cursor_moved (clientId="cli_2", blockId="b1", selStart=3) - Room-->>UserA: cursor_moved (clientId="cli_2", blockId="b1") - Note over UserA: Farbiger Cursor-Avatar erscheint an b1 - - Note over Room: 1.5s InaktivitĂ€t ohne neue TastenanschlĂ€ge - Room->>DB: Debounce-Flush (blocks, Regex #tags -> block_tags, markdown_cache) -``` - -### 4.2 Strikter Echo-Schutz (`clientId`) -- Jeder Client generiert beim Start einen flĂŒchtigen SchlĂŒssel (`clientIdRef`, z. B. `cli_a8f9_l0z3`). -- Der Go-Hub filtert beim Broadcasten im Raum alle Clients heraus, deren Verbindung oder `clientId` mit dem Absender ĂŒbereinstimmt: - ```go - if evt.ClientID != "" && client.clientID == evt.ClientID { - continue - } - ``` -- **Vorteil:** Verhindert Cursor-Resets, Fokus-Verluste und Race Conditions durch Selbstechos wĂ€hrend des lokalen Tippens. - -### 4.3 1.5-Sekunden Debounce-Flush in SQLite -- Das Tippen im Editor erfolgt latenzfrei direkt im Arbeitsspeicher des `DocRoom`. -- Sobald 1,5 Sekunden lang keine neuen TastenanschlĂ€ge oder Mutationen eintreffen (oder wenn der letzte Client den Raum verlĂ€sst), stĂ¶ĂŸt der Raum einen synchronisierten Flush an: - 1. Persistiert alle Blöcke in `blocks`. - 2. Extrahiert alle Hashtags (`#tag`) per Regex und aktualisiert `block_tags`. - 3. Kompiliert die Blockliste (anhand der `level`-Tiefe) in Standard-Markdown und speichert sie in `documents.markdown_cache`. - ---- - -## 🔒 5. Berechtigungen & Bearbeitungsrechte - -- **Kollaboration ohne blockierende Locks:** Alle Nutzer mit Schreibrechten (`permission === 'owner'` oder `'write'`) können zeitgleich im selben Dokument tippen. Pessimistische Locks oder Lease-Heartbeats wurden vollstĂ€ndig entfernt. -- **Schreibschutz:** VerfĂŒgt ein Nutzer nur ĂŒber Leserechte (`permission === 'read'`), ist das Eingabefeld gesperrt (`readOnly`). Er kann das Dokument jedoch in Echtzeit mitlesen und sieht die Cursors der Bearbeiter. - ---- - -## 🔄 6. Fallback-Strategie & Proxy-Resilienz (Dual-Transport Architektur) - -In restriktiven Firmennetzwerken, hinter Unternehmens-Proxies (z.B. Zscaler, BlueCoat) oder fehlerhaft konfigurierten Reverse-Proxies wird der `Upgrade: websocket`-Header hĂ€ufig verworfen (`NS_ERROR_CONNECTION_REFUSED`, Code 1006 / 1002 / 1008). - -MindSift löst dieses Problem durch eine **vollwertige Dual-Transport-Architektur** ([FLAW-03](../flaws.md)): - -``` - ┌───────────────────────────────┐ - │ MindSift Frontend │ - └───────────────┬───────────────┘ - │ - ┌─────────────────────┮─────────────────────┐ - â–Œ â–Œ - Standard: WebSockets Fallback: SSE + REST - (wss://.../api/v1/ws) (https://.../api/v1/events) - │ │ - │ [Proxy Upgrade Block (Code 1006/Timeout)]│ - │ ────────────────────────────────────────> │ - â–Œ â–Œ - ┌────────────────────────┐ ┌────────────────────────┐ - │ Gorilla WS Conn │ │ Server-Sent Events │ - │ Bidirektionaler │ │ (text/event-stream) │ - │ TCP-WebSocket │ │ Standard HTTPS Port │ - └────────────┬───────────┘ └────────────┬───────────┘ - │ │ - └─────────────────────┬─────────────────────┘ - â–Œ - ┌─────────────────────────────┐ - │ Realtime Hub │ - │ (Multiplexing WS & SSE) │ - └─────────────────────────────┘ -``` - -### 1. Automatischer SSE-Fallback (`GET /api/v1/events`) -- Scheitert der WebSocket-Handshake oder blockiert ein Proxy das Upgrade, wechselt MindSift innerhalb von 3,5 Sekunden automatisch auf einen **Server-Sent Events (SSE) Stream**. -- Da SSE ein standardmĂ€ĂŸiger HTTP/1.1- bzw. HTTP/2-GET-Request ĂŒber Standard-Port 443/80 ist (`Content-Type: text/event-stream`), wird er von jedem Proxy und jeder Firewall ungehindert durchgeleitet. -- Header `X-Accel-Buffering: no` und `Cache-Control: no-cache` verhindern Pufferung im Proxy; periodische Ping-Kommentare (`: ping\n\n`) alle 15 Sekunden halten Timeouts fern. -- Der Client bleibt dadurch **vollstĂ€ndig echtzeitfĂ€hig** (`Live (SSE)`). - -### 2. REST-Endpunkte fĂŒr Upstream-Aktionen im SSE-Fallback-Modus -Da SSE unidirektional (Server ➔ Client) ist, stellt MindSift fĂŒr SchreibvorgĂ€nge im SSE-Modus REST-Äquivalente bereit: -- `PATCH /api/v1/documents/:id/blocks`: Akzeptiert Block-Mutationen (`upserted`, `deletedIds`, `blockOrder`) und synchronisiert den In-Memory-Raum sowie SQLite. -- `PUT /api/v1/documents/:id`: Aktualisiert Dokumenttitel und Metadaten. -- `POST /api/v1/events/rooms`: Erlaubt SSE-Clients, RĂ€umen dynamisch beizutreten (`join`) oder sie zu verlassen (`leave`). - -### 3. Verbindungs-Indikator -- **GrĂŒn pulsierend:** Live verbunden ĂŒber WebSockets (`Live (WebSocket)`). -- **TĂŒrkis pulsierend:** Live verbunden ĂŒber Server-Sent Events (`Live (Server-Sent Events / Proxy-Stream)`). -- **Gelb/Amber:** TemporĂ€re Unterbrechung mit automatischem HTTP-Polling-Schutz. - -AusfĂŒhrliche Konfigurationsbeispiele fĂŒr Nginx, Traefik, Caddy, Cloudflare und Apache finden sich im separaten Handbuch: -👉 **[Reverse-Proxy-Konfigurationshandbuch](../reverse-proxy.md)** +## Verhalten im Frontend +- Die globale Verbindung (ein Singleton) leitet jede Nachricht als Browser-Ereignis `mindsift:ws` weiter; Ansichten (`TasksView`, `WorkspaceView`, `SharedListsView`) hören darauf und laden bzw. patchen ihren Zustand. +- Nach einem Abbruch verbindet sich der Client mit Verzögerung neu. +- Weil Notizen nur vom EigentĂŒmer geschrieben werden, dient `DOC_BLOCKS_PATCHED` vor allem dazu, Lesenden und weiteren GerĂ€ten des EigentĂŒmers Änderungen zu zeigen. Es gibt keine Konfliktauflösung (letzter Schreiber gewinnt). diff --git a/Doku/architecture.md b/Doku/architecture.md index 2dd336f..65f6d2d 100644 --- a/Doku/architecture.md +++ b/Doku/architecture.md @@ -1,142 +1,87 @@ -# đŸ›ïž Systemarchitektur & Technologie-Stack +# Architektur -Dieses Dokument beschreibt die interne Architektur von MindSift, die Interaktion zwischen den Systemkomponenten, das Concurrency- und Thread-Safety-Modell sowie die Deployment-Strategie. - ---- - -## đŸ§© 1. Schichten-Architektur - -MindSift ist als monolithische, hochperformante Single-Binary-Lösung konzipiert. Das Go-Backend liefert sowohl die REST- und WebSocket-Schnittstellen aus als auch die vorkompilierte React Single Page Application (SPA) als eingebettetes statisches Dateisystem (`embed.FS`). +## Überblick ``` -┌────────────────────────────────────────────────────────┐ -│ Frontend (React 18) │ -│ Vite ‱ TypeScript ‱ Tailwind CSS ‱ Lucide ‱ Motion │ -└───────────────────────────┬────────────────────────────┘ - │ HTTPS REST / WSS WebSocket -┌───────────────────────────▌────────────────────────────┐ -│ Go Chi Routing Layer │ -│ CORS ‱ RealIP ‱ Recoverer ‱ Auth / Role Guard │ -â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”Źâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ -│ Business Engine │ Gorilla WebSocket │ -│ Triage ‱ Tasks ‱ Alarms │ Rooms ‱ Hub ‱ Doc-Locking │ -├───────────────────────────┎───────────────────────────── -│ Storage Abstraction │ -│ sqlite.go (modernc.org/sqlite pure Go, WAL-Mode) │ -└───────────────────────────┬────────────────────────────┘ - │ - ┌─────────────┮─────────────┐ - â–Œ â–Œ - /data/mindsift.db /data/workspace/ - (SQLite Datenbank) (Physische .md Dateien) +Browser (React SPA, PWA-Manifest) + │ REST /api/v1/* Authorization: Bearer + │ 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](../cmd/server/main.go)). Unbekannte Pfade liefern `index.html` (SPA-Routing); `index.html`, `sw.js` und `manifest.json` werden mit `no-store` ausgeliefert. -## đŸ› ïž 2. Technologie-Stack +## Stack -### Backend (Go 1.23) -- **HTTP Routing:** [`github.com/go-chi/chi/v5`](https://github.com/go-chi/chi) – Minimalistischer, idiomatisch schneller Router mit Middleware-Ketten. -- **WebSocket Hub:** [`github.com/gorilla/websocket`](https://github.com/gorilla/websocket) – Verbindungs-Upgrade, Keep-Alive/Ping-Pong und Room-Multiplexing. -- **Datenbank-Treiber:** [`modernc.org/sqlite`](https://gitlab.com/cznic/sqlite) – CGO-freier, rein in Go ĂŒbersetzter SQLite-Treiber fĂŒr einfache plattformĂŒbergreifende Kompilierung und Containerisierung ohne GCC-Toolchain. -- **Authentifizierung:** [`github.com/golang-jwt/jwt/v5`](https://github.com/golang-jwt/jwt) (HMAC-SHA256 Token) und [`golang.org/x/crypto/bcrypt`](https://pkg.go.dev/golang.org/x/crypto/bcrypt) fĂŒr Passwort-Hashing. +| 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` | -### Frontend (React 18 SPA) -- **Build-Tool:** [Vite 5](https://vitejs.dev/) fĂŒr schnelles Hot-Module-Replacement (HMR) und optimierte Production-Bundles. -- **Sprache:** TypeScript 5 mit strikter Typisierung. -- **Design & Layout:** [Tailwind CSS 3](https://tailwindcss.com/) in Verbindung mit dem MindSift Single-File CSS Theming System. -- **Icons & Animationen:** [Lucide React](https://lucide.dev/) und [Framer Motion](https://www.framer.com/motion/) fĂŒr Gesten, Karten-Swipes und ÜbergĂ€nge. +## 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/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/themes/` | CSS pro Theme | -## 📁 3. Verzeichnisstruktur des Projekts +## 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) 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](flaws.md) | +| `BASE_URL` | `http://localhost:` | 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 | + +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_`, 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](flaws.md)). +- **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](modules/tasks.md), [FLAW-12](flaws.md). + +## Build ``` -MindSift/ -├── cmd/ -│ └── server/ -│ └── main.go # Einstiegspunkt: Initialisierung, Config, Store, Start -├── internal/ -│ ├── api/ -│ │ ├── server.go # HTTP-Routen, Handler-Methoden, Middleware -│ │ └── server_test.go # Integrationstests fĂŒr alle REST- und Ingest-Routen -│ ├── auth/ -│ │ ├── auth.go # JWT Validierung, Token-Erstellung, Context-Helpers -│ │ └── apikey.go # API-Key Erzeugung (mindsift_sec_...) & SHA-256 Hashing -│ ├── config/ -│ │ └── config.go # Environment-Variablen (PORT, DATA_DIR, JWT_SECRET, SMTP) -│ ├── domain/ -│ │ └── models.go # Go-Structs fĂŒr Thought, Document, List, User, Team etc. -│ ├── storage/ -│ │ ├── sqlite.go # DB-Schema, CRUD-Operationen, Queries, Migrationen -│ │ └── storage_test.go # Unit-Tests fĂŒr Speicheroperationen -│ └── ws/ -│ └── hub.go # WebSocket Client, Room-Management, Locking-Mechanismus -├── frontend/ -│ ├── src/ -│ │ ├── components/ # Wiederverwendbare UI-Elemente (Modals, Avatar, MenĂŒs) -│ │ ├── context/ # MobileActionContext (Mobile Bottom Bar State) -│ │ ├── features/ -│ │ │ ├── auth/ # SetupWizard & Login -│ │ │ ├── capture/ # InstantCaptureBar (Schnelleingabe) -│ │ │ ├── help/ # Integrierte Benutzerdokumentation -│ │ │ ├── lists/ # Geteilte Listen & Einkauf -│ │ │ ├── settings/ # Benutzer-, Admin- & Theme-Einstellungen -│ │ │ ├── teams/ # Gruppen & Mitglieder-Management -│ │ │ ├── triage/ # Triage-Deck, Aufgabenliste, Kanban & TaskModal -│ │ │ └── workspace/ # Outliner-Editor, Datei-Explorer, Wiki & Rollup -│ │ ├── lib/ # api.ts (HTTP/WS Client), types.ts, theme.ts -│ │ ├── themes/ # EigenstĂ€ndige Theme CSS-Dateien (dark, light, etc.) -│ │ ├── App.tsx # Root-Komponente, Navigation, Top/Bottom Bars -│ │ └── main.tsx # React DOM Bootstrap -│ ├── package.json -│ └── vite.config.ts -├── Doku/ # Dieses technische Wiki -├── Dockerfile # Multi-Stage Build Definition -└── compose.yaml # Docker Compose Vorlage fĂŒr Produktivbetrieb +Stage 1 node:22-alpine npm ci && npm run 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 ``` ---- - -## ⚡ 4. Thread-Safety & Concurrency-Modell - -Da SQLite Datei-basiert arbeitet und Go standardmĂ€ĂŸig hochgradig nebenlĂ€ufig (Multi-Threaded via Goroutines) operiert, sind folgende Schutzmaßnahmen implementiert: - -### 1. SQLite Pragmas & WAL-Modus -Im Storage-Layer ([`internal/storage/sqlite.go`](../internal/storage/sqlite.go)) wird die Datenbankverbindung beim Start zwingend mit folgenden DSN-Pragmas konfiguriert: -```go -dsn := filepath.Join(dataDir, "mindsift.db") + - "?_pragma=busy_timeout(10000)&" + - "_pragma=journal_mode(WAL)&" + - "_pragma=synchronous(NORMAL)&" + - "_pragma=foreign_keys(1)" -``` -- **`journal_mode(WAL)`:** Write-Ahead Logging erlaubt parallele Leser (`SELECT`), wĂ€hrend ein Schreiber (`INSERT`/`UPDATE`) aktiv ist. Leser blockieren niemals Schreiber und Schreiber blockieren niemals Leser. -- **`busy_timeout(10000)`:** Sollte ein Schreibvorgang kurzzeitig blockiert sein, wartet SQLite bis zu 10.000 ms (10 Sekunden), bevor ein `SQLITE_BUSY` Fehler geworfen wird. -- **`foreign_keys(1)`:** Erzwingt referenzielle IntegritĂ€t und automatische Kaskadierungs-Löschungen (`ON DELETE CASCADE`). - -### 2. Connection Pool Begrenzung -SQLite unterstĂŒtzt nur einen einzigen Schreib-Thread zur gleichen Zeit: -```go -db.SetMaxOpenConns(8) -db.SetMaxIdleConns(4) -db.SetConnMaxLifetime(time.Hour) -``` - -### 3. Cursor-Deadlock-Vermeidung -> **Kritische Backend-Regel:** Niemals verschachtelte Abfragen bei offenem `*sql.Rows`-Cursor ausfĂŒhren! -In Go hĂ€lt ein offener Cursor eine Verbindung aus dem Pool besetzt. FĂŒhrt der Code innerhalb von `for rows.Next()` eine weitere Abfrage auf `s.db` aus, kann der Connection-Pool bei mehreren parallelen Benutzern innerhalb von Millisekunden einfrieren. -*Pattern:* Alle Zeilen werden sofort vollstĂ€ndig in ein Go-Slice gelesen, `rows.Close()` wird ausgefĂŒhrt, und erst danach werden verknĂŒpfte EntitĂ€ten geladen. - ---- - -## 🌐 5. Deployment & Container-Architektur - -MindSift wird als schlanker, autarker Docker-Container ausgeliefert: -1. **Stage 1 (Frontend Build):** `node:20-alpine` kompiliert TypeScript und generiert statische Assets nach `frontend/dist/`. -2. **Stage 2 (Backend Build):** `golang:1.23-alpine` bettet `frontend/dist/` via `//go:embed` ein, fĂŒhrt Unit-Tests aus und kompiliert ein statisches Binary (`mindsift-server`). -3. **Stage 3 (Production Runtime):** `alpine:3.20` mit `ca-certificates` und `tzdata`. Das finale Image ist ca. 45 MB groß und benötigt keine externen Datenbankserver oder AbhĂ€ngigkeiten. - -Alle persistenten Daten befinden sich in einem einzigen Verzeichnis `/data`: -- `/data/mindsift.db` (SQLite Hauptdatenbank inklusive WAL-Journal und Shared-Memory-Dateien) -- Notizen und Workspace-Blöcke werden zu 100% in der SQLite-Datenbank persistiert (keine losen Markdown-Dateien auf der Festplatte). +Die Tests laufen beim Image-Build (`go test -v ./...`); ein Fehler bricht den Build ab. Frontend-Tests existieren nicht ([FLAW-19](flaws.md)). diff --git a/Doku/database.md b/Doku/database.md index 3447799..eaccf8c 100644 --- a/Doku/database.md +++ b/Doku/database.md @@ -1,373 +1,83 @@ -# đŸ—„ïž Datenbank-Design & SQLite Schema +# Datenbank -MindSift verwendet als persistente Datenspeicherung eine lokale SQLite-Datenbank (`/data/mindsift.db`). Dieses Dokument beschreibt das relationale Datenmodell, Tabellenstrukturen, Indizes, FremdschlĂŒsselbeziehungen und das Migrationskonzept. +SQLite-Datei `mindsift.db` (Standard: `$DATA_DIR/mindsift.db`), geöffnet mit `journal_mode=WAL`, `synchronous=NORMAL`, `foreign_keys=ON`, `busy_timeout=10000` und höchstens 8 Verbindungen. Das Schema entsteht in `Store.migrate()` ([sqlite.go](../internal/storage/sqlite.go)): zuerst `CREATE TABLE IF NOT EXISTS`, danach eine Liste idempotenter `ALTER TABLE`/`CREATE`-Anweisungen fĂŒr Ă€ltere Datenbanken. IDs sind Text mit PrĂ€fix (`usr_
`, `th_
`, `tkl_
`, `lst_
`, `tls_
`, `blk_
`, `doc_
`). ---- +Die Datei `migrations/001_init.sql` wird vom Code **nicht** gelesen und ist veraltet ([FLAW-13](flaws.md)). -## 📊 1. Entity-Relationship-Diagramm (ERD) +## Tabellen -```mermaid -erDiagram - users ||--o{ thoughts : "erstellt" - users ||--o{ documents : "besitzt" - users ||--o{ lists : "besitzt" - users ||--o{ task_lists : "besitzt" - users ||--o{ api_keys : "besitzt" - users ||--o{ team_members : "ist Mitglied in" +### Benutzer und Zugang +| Tabelle | Zweck | Wichtige Spalten | +|---|---|---| +| `users` | Konten | `email` (eindeutig), `username`, `password_hash`, `display_name`, `role` (`admin`/`member`), `is_active`, `token_version`, `alarm_channels` (kommagetrennt), `alarm_email`, `discord_webhook_url`, `custom_webhook_url`, `notify_before_minutes`, `avatar_url` | +| `user_invites` | Einladungscodes | `code`, `email`, `role`, `team_id`, `expires_at`, `used_by` | +| `revoked_tokens` | Sperrliste ausgeloggter JWTs | `token_hash`, `expires_at` | +| `api_keys` | Webhook-/API-SchlĂŒssel | `key_prefix`, `key_hash` (SHA-256), `scopes` (nicht ausgewertet), `last_used_at` | +| `system_settings` | SchlĂŒssel/Wert fĂŒr Instanz-Einstellungen | `sharing_mode`, `allow_registration`, `instance_name`, `smtp_*` | - teams ||--o{ team_members : "besteht aus" - teams ||--o{ task_list_shares : "erhĂ€lt Zugriff" - teams ||--o{ list_shares : "erhĂ€lt Zugriff" - teams ||--o{ doc_team_shares : "erhĂ€lt Zugriff" - teams ||--o{ workspace_folder_shares : "erhĂ€lt Zugriff" +### Gedanken und Aufgaben +| Tabelle | Zweck | Wichtige Spalten | +|---|---|---| +| `thoughts` | Gedanken **und** Aufgaben in einer Tabelle | `status` (`inbox`/`kept`/`converted`/`discarded`), `converted_type` (`task`/`document`/`list_item`/`document_block`), `converted_ref_id`, `priority`, `is_completed`, `due_date` (nur Datum), `task_list_id`, `kanban_status`, `notes`, `subtasks` (JSON), `tags` (JSON), `alarm_sent`, `sort_order`, `source` (`manual`, `voice`, `webhook` 
) | +| `task_lists` | Aufgabenlisten | `user_id` (EigentĂŒmer), `team_id` (Altfeld), `title`, `icon`, `color`, `columns` (JSON, Kanban-Spalten) | +| `task_list_shares` | Freigaben von Aufgabenlisten | `target_type` (`team`/`user`), `target_id`, **`permission`** (`editor`/`viewer`, Standard `editor`) | - task_lists ||--o{ thoughts : "gruppiert" - task_lists ||--o{ task_list_shares : "wird geteilt mit" +Eine Aufgabe ist ein `thought` mit `status='kept'` oder `status='converted' AND converted_type='task'`. Aufgaben, die aus einem Notiz-Block entstehen, tragen `converted_type='document_block'` und `converted_ref_id=':'`. - lists ||--o{ list_items : "enthĂ€lt" - lists ||--o{ list_shares : "wird geteilt mit" +### Workspace +| Tabelle | Zweck | +|---|---| +| `documents` | Notizen: `title`, `icon`, `folder_path`, `tags` (JSON), `is_archived`, `markdown_cache`, `shared_folder_id`, `sub_folder_path`, Altspalten `content`, `blocks_json` | +| `blocks` | Outliner-Blöcke: `document_id`, `type`, `content`, `level`, `position` (REAL), `checked`, `collapsed`, `lang` | +| `block_tags` | `#Hashtags` je Block (`tag`, `document_id`, `block_id`) | +| `tag_pages` | Freitext-Notiz je Benutzer und Tag | +| `doc_pins` | Favoriten je Benutzer | +| `doc_shares`, `doc_team_shares` | Notiz-Freigaben an Benutzer bzw. Gruppen (`permission` effektiv immer `read`) | +| `workspace_folders` | Explizit angelegte Ordnerpfade | +| `workspace_shared_folders` | Geteilte Ordner (EntitĂ€t mit EigentĂŒmer) | +| `workspace_folder_shares` | Freigaben geteilter Ordner | +| `workspace_folder_mounts` | Persönlicher EinhĂ€ngepfad je EmpfĂ€nger und geteiltem Ordner | +| `doc_user_folders` | Persönliche Ablage einer geteilten Einzelnotiz je EmpfĂ€nger | - documents ||--o{ blocks : "besteht aus" - documents ||--o{ block_tags : "indiziert" - blocks ||--o{ block_tags : "verknĂŒpft" - documents ||--o{ doc_shares : "geteilt mit Benutzern" - documents ||--o{ doc_team_shares : "geteilt mit Teams" - documents ||--o{ doc_user_folders : "eigene Platzierung" - documents ||--o{ doc_pins : "Favorit von" +Blocktypen: `h1`, `h2`, `h3`, `paragraph`, `text`, `todo`, `bullet`, `code`, `callout`, `divider`, `table`, `progress`. - workspace_shared_folders ||--o{ workspace_folder_shares : "wird geteilt mit" - workspace_shared_folders ||--o{ workspace_folder_mounts : "eingehĂ€ngt bei" - workspace_shared_folders ||--o{ documents : "enthĂ€lt" +### Listen +| Tabelle | Zweck | +|---|---| +| `lists` | Geteilte Listen: `title`, `icon`, `list_type` (`shopping`/`checklist`/`vacation`) | +| `list_shares` | Freigaben: `target_type`, `target_id`, `role` (`editor`/`viewer`) | +| `list_items` | EintrĂ€ge: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `sort_order` | + +### Gruppen +| Tabelle | Zweck | +|---|---| +| `teams` | `name`, `description`, `org_id` (Standard `default`) | +| `team_members` | `team_id`, `user_id`, `role` (`admin`/`member`) | +| `organizations` | RudimentĂ€r; wird vom UI nicht mehr benötigt ([FLAW-24](flaws.md)) | + +## Beziehungen + +``` +users 1─n thoughts thoughts n─1 task_lists (task_list_id, lose) +users 1─n task_lists task_lists 1─n task_list_shares ─(team|user)→ teams/users +users 1─n documents 1─n blocks 1─n block_tags +documents n─n users via doc_shares / doc_team_shares (nur Lesen) +workspace_shared_folders 1─n workspace_folder_shares / workspace_folder_mounts +users 1─n lists 1─n list_items; lists 1─n list_shares +teams 1─n team_members n─1 users ``` ---- +`thoughts.task_list_id` ist keine FremdschlĂŒsselbeziehung; beim Löschen einer Liste setzt der Code die Verweise auf `''`. -## 📑 2. Detaillierte Tabellen-Spezifikation +## Migrationen und AufrĂ€umarbeiten beim Start -### 2.1 Benutzerverwaltung, System & Sicherheit +- Spalten/Tabellen/Indizes werden nachgezogen (`ALTER TABLE 
 ADD COLUMN`, Fehler werden ignoriert). +- Veraltete Tabellen werden gelöscht: `spaces`, `space_members`, `space_team_shares`, `document_crdt_state`, `document_crdt_updates`, `document_blocks`, `doc_block_tags`. +- `users.username` wird aus dem E-Mail-PrĂ€fix aufgefĂŒllt. +- Alle Dokument-, Team- und Ordnerfreigaben werden auf `read` gesetzt (bei **jedem** Start, siehe [FLAW-17](flaws.md)). +- Standardwerte in `system_settings` (`INSERT OR IGNORE`): `sharing_mode=teams_only`, `allow_registration=false`, `instance_name=MindSift`, SMTP leer. +- Die Tabelle `teams` wird einmalig ohne FremdschlĂŒssel auf `organizations` neu aufgebaut. -#### `users` -Verwaltet Konten, Rollen, Multi-Channel Alarmziele und BenutzerprĂ€ferenzen. -```sql -CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, -- PrĂ€fix "usr_..." - email TEXT UNIQUE NOT NULL, -- E-Mail-Adresse - username TEXT NOT NULL DEFAULT '', -- Eindeutiger Login-Name - password_hash TEXT NOT NULL, -- bcrypt Hash - display_name TEXT NOT NULL, -- Anzeigename - avatar_url TEXT NOT NULL DEFAULT '', -- Profilbild URL oder leer - role TEXT NOT NULL DEFAULT 'member', -- 'admin' | 'member' - is_active INTEGER NOT NULL DEFAULT 1, -- 1 = aktiv, 0 = gesperrt - token_version INTEGER NOT NULL DEFAULT 1, -- Erhöht bei Passwortwechsel/Rechteentzug zur Token-Invalidierung - alarm_channels TEXT NOT NULL DEFAULT 'discord', -- Kommagetrennt: 'email', 'discord', 'webhook' - alarm_email TEXT NOT NULL DEFAULT '', -- Ziel-E-Mail fĂŒr Alarme - discord_webhook_url TEXT NOT NULL DEFAULT '', -- Discord Webhook URL - custom_webhook_url TEXT NOT NULL DEFAULT '', -- HTTP POST JSON Webhook URL - notify_before_minutes INTEGER NOT NULL DEFAULT 30, -- Vorwarnzeit in Minuten - created_at TEXT NOT NULL, -- ISO 8601 String - updated_at TEXT NOT NULL -); -CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); -CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); -``` +## Sicherung -#### `system_settings` -Globale Instanz-Konfiguration (Key-Value-Tabelle). -```sql -CREATE TABLE IF NOT EXISTS system_settings ( - key TEXT PRIMARY KEY, -- 'sharing_mode', 'allow_registration', 'instance_name', 'smtp_...' - value TEXT NOT NULL -); -``` - -#### `user_invites` -Einladungscodes fĂŒr kontrollierte Benutzer-Registrierungen. -```sql -CREATE TABLE IF NOT EXISTS user_invites ( - id TEXT PRIMARY KEY, -- PrĂ€fix "inv_..." - code TEXT UNIQUE NOT NULL, -- Format "MS-XXXXXXXX" - email TEXT DEFAULT '', -- Optional: an bestimmte E-Mail gebunden - role TEXT NOT NULL DEFAULT 'member', -- Rolle des neuen Kontos ('member', 'admin') - created_by TEXT NOT NULL, -- Ersteller User-ID - used_by TEXT DEFAULT '', -- Eingelöste User-ID - expires_at TEXT NOT NULL, -- GĂŒltig bis (ISO 8601) - created_at TEXT NOT NULL -); -CREATE INDEX IF NOT EXISTS idx_user_invites_code ON user_invites(code); -``` - -#### `api_keys` -Persönliche API-SchlĂŒssel fĂŒr externe Skripte, Smarthome und Ingestion. -```sql -CREATE TABLE IF NOT EXISTS api_keys ( - id TEXT PRIMARY KEY, -- PrĂ€fix "key_..." - user_id TEXT NOT NULL, - name TEXT NOT NULL, -- Bezeichnung (z.B. "Home Assistant") - key_prefix TEXT NOT NULL, -- Erste 12 Zeichen im Klartext fĂŒr Anzeige - key_hash TEXT UNIQUE NOT NULL, -- SHA-256 Hash des kompletten SchlĂŒssels - scopes TEXT NOT NULL DEFAULT 'ingest,read', - last_used_at TEXT NOT NULL DEFAULT '', - created_at TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); -``` - -#### `revoked_tokens` -Sperrliste fĂŒr explizit abgemeldete JWTs (Blacklist bis zum regulĂ€ren Token-Ablaufdatum). -```sql -CREATE TABLE IF NOT EXISTS revoked_tokens ( - token_hash TEXT PRIMARY KEY, -- SHA-256 Hash des Tokens - user_id TEXT NOT NULL, -- Benutzer-ID - expires_at TEXT NOT NULL, -- ISO 8601 Ablaufdatum - revoked_at TEXT NOT NULL -- ISO 8601 Sperrzeitpunkt -); -CREATE INDEX IF NOT EXISTS idx_revoked_tokens_expires ON revoked_tokens(expires_at); -CREATE INDEX IF NOT EXISTS idx_revoked_tokens_user ON revoked_tokens(user_id); -``` - ---- - -### 2.2 Gruppen & Organisations-Teams - -#### `organizations` & `teams` & `team_members` -```sql -CREATE TABLE IF NOT EXISTS organizations ( - id TEXT PRIMARY KEY, -- PrĂ€fix "org_..." - name TEXT NOT NULL, - owner_id TEXT NOT NULL, - created_at TEXT NOT NULL, - FOREIGN KEY(owner_id) REFERENCES users(id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS teams ( - id TEXT PRIMARY KEY, -- PrĂ€fix "tm_..." - org_id TEXT NOT NULL DEFAULT 'default', - name TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '', - created_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS team_members ( - team_id TEXT NOT NULL, - user_id TEXT NOT NULL, - role TEXT NOT NULL DEFAULT 'member', -- 'admin' | 'member' - joined_at TEXT NOT NULL, - PRIMARY KEY (team_id, user_id), - FOREIGN KEY(team_id) REFERENCES teams(id) ON DELETE CASCADE, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); -``` - ---- - -### 2.3 Gedanken, Triage & Aufgaben - -In MindSift nutzen Quick Dumps (Triage) und Aufgaben dieselbe Basistabelle **`thoughts`**. Ein Quick Dump ist ein Gedanke mit `status = 'inbox'`, der durch Triage in `status = 'kept'` oder `status = 'converted'` ĂŒbergeht. - -#### `thoughts` -```sql -CREATE TABLE IF NOT EXISTS thoughts ( - id TEXT PRIMARY KEY, -- PrĂ€fix "th_..." - user_id TEXT NOT NULL, - content TEXT NOT NULL, -- Textinhalt inkl. Hashtags - tags TEXT NOT NULL DEFAULT '[]', -- JSON Array: ["smarthome", "keller"] - priority TEXT NOT NULL DEFAULT 'normal', -- 'low' | 'normal' | 'high' | 'urgent' - source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'voice' | 'webhook' - status TEXT NOT NULL DEFAULT 'inbox', -- 'inbox' | 'kept' | 'discarded' | 'converted' - is_completed INTEGER NOT NULL DEFAULT 0, -- 0 = offen, 1 = erledigt - converted_type TEXT DEFAULT '', -- 'task' | 'document' | 'list_item' - converted_ref_id TEXT DEFAULT '', -- VerknĂŒpfte ID (z.B. doc_id oder list_item_id) - due_date TEXT DEFAULT '', -- FĂ€lligkeitsdatum "YYYY-MM-DD" - task_list_id TEXT NOT NULL DEFAULT '', -- Zugeordnete Aufgabenliste ("" = Standard) - kanban_status TEXT NOT NULL DEFAULT 'todo',-- 'todo' | 'in_progress' | 'done' | column.id - notes TEXT NOT NULL DEFAULT '', -- ZusĂ€tzliche Notizen / Beschreibung - subtasks TEXT NOT NULL DEFAULT '[]', -- JSON Array von SubTask: [{id, title, isCompleted}] - alarm_sent INTEGER NOT NULL DEFAULT 0, -- 1 = Alarm wurde bereits versendet - sort_order INTEGER NOT NULL DEFAULT 0, -- Manuelle Reorder-Sortierung - created_at TEXT NOT NULL, - triaged_at TEXT DEFAULT '', - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_thoughts_user_status ON thoughts(user_id, status, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_thoughts_list_sort ON thoughts(task_list_id, sort_order ASC, created_at DESC); -``` - -#### `task_lists` & `task_list_shares` -```sql -CREATE TABLE IF NOT EXISTS task_lists ( - id TEXT PRIMARY KEY, -- PrĂ€fix "tl_..." - user_id TEXT NOT NULL, - title TEXT NOT NULL, - icon TEXT NOT NULL DEFAULT '📋', - color TEXT NOT NULL DEFAULT '#3b82f6', - team_id TEXT NOT NULL DEFAULT '', -- Optional: an Team gebunden - columns TEXT NOT NULL DEFAULT '[]', -- JSON Array von Kanban-Spalten - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS task_list_shares ( - id TEXT PRIMARY KEY, - task_list_id TEXT NOT NULL, - target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')), - target_id TEXT NOT NULL, -- Team-ID oder User-ID - created_at TEXT NOT NULL, - created_by TEXT NOT NULL, - UNIQUE(task_list_id, target_type, target_id), - FOREIGN KEY(task_list_id) REFERENCES task_lists(id) ON DELETE CASCADE -); -``` - ---- - -### 2.4 Workspace: Dokumente, Blöcke & Geteilte Ordner - -#### `documents`, `blocks` & `block_tags` (Database-First Outliner Schema) -Dokumente werden als Kopfdatensatz in `documents` gespeichert, wĂ€hrend der Inhalt als feingranulare Blöcke in `blocks` verwaltet wird. Hashtags (#tags) werden per Regex analysiert und in `block_tags` abgelegt. MindSift speichert alle Notizen zu 100% in SQLite (keine physischen `.md`-Dateien auf Festplatte). - -```sql -CREATE TABLE IF NOT EXISTS documents ( - id TEXT PRIMARY KEY, -- PrĂ€fix "doc_..." - user_id TEXT NOT NULL DEFAULT '', -- EigentĂŒmer (usr_...) - space_id TEXT DEFAULT '', - title TEXT NOT NULL DEFAULT '', -- Dokumenttitel - folder_id TEXT NOT NULL DEFAULT '', -- Ordner-Zuordnung - markdown_cache TEXT NOT NULL DEFAULT '', -- Kompiliertes Standard-Markdown (automatischer Cache) - icon TEXT NOT NULL DEFAULT '📄', -- Emoji-Icon - content TEXT NOT NULL DEFAULT '', -- Plaintext / Text-ReprĂ€sentation - blocks_json TEXT NOT NULL DEFAULT '[]', -- Legacy/Zusatz-Cache - tags TEXT NOT NULL DEFAULT '[]', -- JSON Array von Tags - folder_path TEXT NOT NULL DEFAULT '', -- Relativer Ordnerpfad des EigentĂŒmers - is_pinned INTEGER NOT NULL DEFAULT 0, -- Globaler Pin - is_archived INTEGER NOT NULL DEFAULT 0, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_documents_user ON documents(user_id, is_archived, updated_at DESC); - -CREATE TABLE IF NOT EXISTS blocks ( - id TEXT PRIMARY KEY, -- Block-ID (z.B. "blk_..." oder "docId-b0") - document_id TEXT NOT NULL, -- Zugehöriges Dokument (doc_...) - content TEXT NOT NULL DEFAULT '', -- Textinhalt des Blocks - level INT NOT NULL DEFAULT 0, -- Hierarchieebene (0 = Root, 1 = Kind...) - position REAL NOT NULL DEFAULT 0, -- Fließkommazahl fĂŒr konfliktfreies EinfĂŒgen/Sortieren - type TEXT NOT NULL DEFAULT 'bullet', -- 'bullet', 'todo', 'h1', 'h2', 'h3', 'code', 'quote', 'callout', 'divider' - checked INTEGER NOT NULL DEFAULT 0, -- FĂŒr Checklisten (0 = offen, 1 = abgehakt) - collapsed INTEGER NOT NULL DEFAULT 0, -- FĂŒr eingeklappte UnterbĂ€ume (1 = zugeklappt) - lang TEXT NOT NULL DEFAULT '', -- Programmiersprache fĂŒr Codeblöcke - FOREIGN KEY(document_id) REFERENCES documents(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_blocks_doc_pos ON blocks(document_id, position ASC); - -CREATE TABLE IF NOT EXISTS block_tags ( - tag TEXT NOT NULL, -- Hashtag (z.B. "meeting", ohne fĂŒhrendes '#') - document_id TEXT NOT NULL, -- Zugehöriges Dokument - block_id TEXT NOT NULL, -- Zugehöriger Block - PRIMARY KEY(tag, block_id), - FOREIGN KEY(document_id) REFERENCES documents(id) ON DELETE CASCADE, - FOREIGN KEY(block_id) REFERENCES blocks(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_block_tags_tag ON block_tags(tag); -CREATE INDEX IF NOT EXISTS idx_block_tags_doc ON block_tags(document_id); -``` - -#### Entkoppelte Ordner-Freigabe (`workspace_shared_folders` & `workspace_folder_mounts`) -Ermöglicht das Teilen kompletter Ordnerstrukturen. Verschiebt ein EmpfĂ€nger den geteilten Ordner in seiner Hierarchie, wird **nur sein Mount-Pfad** aktualisiert – die Quellstruktur beim EigentĂŒmer und anderen EmpfĂ€ngern bleibt unberĂŒhrt! - -```sql -CREATE TABLE IF NOT EXISTS workspace_shared_folders ( - id TEXT PRIMARY KEY, -- PrĂ€fix "wsf_..." - owner_id TEXT NOT NULL, - name TEXT NOT NULL, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS workspace_folder_shares ( - id TEXT PRIMARY KEY, - folder_id TEXT NOT NULL, - target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')), - target_id TEXT NOT NULL, - permission TEXT NOT NULL CHECK(permission IN ('read', 'write')), - created_by TEXT NOT NULL, - created_at TEXT NOT NULL, - UNIQUE(folder_id, target_type, target_id) -); - -CREATE TABLE IF NOT EXISTS workspace_folder_mounts ( - user_id TEXT NOT NULL, - folder_id TEXT NOT NULL, - mount_path TEXT NOT NULL, -- Eigener lokaler Pfad des Benutzers (z.B. "Arbeit/Projekte/Team-Docs") - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY(user_id, folder_id) -); - -CREATE TABLE IF NOT EXISTS doc_user_folders ( - user_id TEXT NOT NULL, - doc_id TEXT NOT NULL, - folder_path TEXT NOT NULL DEFAULT '', -- Individuelle Ordnerplatzierung fĂŒr geteilte Einzelnotizen - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY(user_id, doc_id) -); -``` - ---- - -### 2.5 Geteilte Listen & Listen-EintrĂ€ge - -#### `lists`, `list_items` & `list_shares` -```sql -CREATE TABLE IF NOT EXISTS lists ( - id TEXT PRIMARY KEY, -- PrĂ€fix "lst_..." - user_id TEXT NOT NULL, - title TEXT NOT NULL, - icon TEXT NOT NULL DEFAULT '🛒', - list_type TEXT NOT NULL DEFAULT 'shopping',-- 'shopping', 'packing', 'inventory', 'todo' - updated_at TEXT NOT NULL, - created_at TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_lists_user ON lists(user_id, updated_at DESC); - -CREATE TABLE IF NOT EXISTS list_shares ( - id TEXT PRIMARY KEY, - list_id TEXT NOT NULL, - target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')), - target_id TEXT NOT NULL, - role TEXT NOT NULL DEFAULT 'editor' CHECK(role IN ('editor', 'viewer')), - created_by TEXT NOT NULL, - created_at TEXT NOT NULL, - UNIQUE(list_id, target_type, target_id), - FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS list_items ( - id TEXT PRIMARY KEY, -- PrĂ€fix "itm_..." - list_id TEXT NOT NULL, - content TEXT NOT NULL, -- Bezeichnung des Eintrags - category TEXT NOT NULL DEFAULT '', -- Regal/Kategorie (z.B. "KĂŒhlregal") - quantity TEXT NOT NULL DEFAULT '', -- Menge (z.B. "2x", "500g") - is_completed INTEGER NOT NULL DEFAULT 0, -- 1 = abgehakt - completed_by TEXT NOT NULL DEFAULT '', -- User-ID des Abhakers - completed_by_name TEXT NOT NULL DEFAULT '',-- Name des Abhakers fĂŒr Live-Badge - sort_order INTEGER NOT NULL DEFAULT 0, - created_at TEXT NOT NULL, - completed_at TEXT NOT NULL DEFAULT '', - FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE -); -CREATE INDEX IF NOT EXISTS idx_list_items_sync ON list_items(list_id, is_completed, sort_order ASC); -``` - ---- - -## 🔄 3. Migrationsstrategie - -MindSift setzt auf **idempotente automatische In-Process-Migrationen** beim Serverstart ([`internal/storage/sqlite.go`](../internal/storage/sqlite.go)): -1. **Schema-Bootstrap:** AusfĂŒhrung des Basis-Schemas via `CREATE TABLE IF NOT EXISTS`. -2. **Column & Index Migration Array:** Ein vordefiniertes Array idempotenter DDL-Befehle (z.B. `ALTER TABLE thoughts ADD COLUMN ...`, `CREATE TABLE IF NOT EXISTS doc_user_folders ...`). -3. **Fehlerbehandlung:** Wenn Spalten bereits existieren (z.B. `"duplicate column name"`), wird der Fehler ignoriert und der Start fortgesetzt. -4. **Vorteil:** Keine manuellen Migrations-Skripte nötig; Container-Updates können jederzeit ohne Downtime oder Vorbereitung ĂŒber bestehende Datenbanken ausgerollt werden. +Die gesamte Instanz besteht aus der Datei `mindsift.db` (plus `-wal`/`-shm` im laufenden Betrieb). Konsistent sichern per `sqlite3 mindsift.db ".backup 'backup.db'"` oder durch Stoppen des Containers und Kopieren des `data/`-Verzeichnisses. Benutzerexport: `GET /api/v1/export` (JSON mit Gedanken, Notizen, Listen, Aufgabenlisten). diff --git a/Doku/flaws.md b/Doku/flaws.md index 3f9c99a..496ebdd 100644 --- a/Doku/flaws.md +++ b/Doku/flaws.md @@ -1,132 +1,173 @@ -# Bekannte Schwachstellen, Risiken & Technische Schulden (Flaws) +# Bekannte Schwachstellen, Risiken & technische Schulden (Flaws) -Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse aller bekannten Limitierungen, Architektur-Kompromisse, Skalierungsrisiken und UX-Schwachstellen in **MindSift**. Sie dient als verbindlicher Backlog fĂŒr zukĂŒnftige Refactorings und Architektur-Meilensteine. +Stand: Code-Review des Branches `dev` (Oktober 2026). Jeder Eintrag wurde gegen den Quellcode geprĂŒft; die Fundstellen sind angegeben. EintrĂ€ge ohne Live-Test sind als *nicht live verifiziert* markiert. + +Schweregrade: **Hoch** (Datenabfluss oder Rechteumgehung möglich), **Mittel** (Fehlfunktion oder Schutz greift nicht), **Niedrig** (Wartbarkeit, Komfort, Hygiene). --- -## 📌 Übersicht & Risikomatrix +## Übersicht -| ID | Bereich | Problem / Schwachstelle | Schweregrad | Risiko im Produktivbetrieb | -|---|---|---|---|---| -| **FLAW-01** | Workspace Sync | Blockierende Locks / Synchronisations-Konflikte | **Gelöst (v2.2)** | Behoben durch In-Memory DocRooms, Echo-Schutz per `clientId` und 1.5s Debounce-Flush | -| **FLAW-02** | Storage | Dual-Storage Drift (SQLite vs. Dateisystem) | **Obsolet / Gelöst** | Kein Risiko: MindSift nutzt Pure-DB (keine `.md`-Dateien auf Platte) | -| **FLAW-03** | Netzwerk / WS | Proxy-InkompatibilitĂ€t bei WebSocket-Upgrades & Polling-Fallback | **Gelöst (v2.2)** | Behoben durch Dual-Transport SSE-Fallback & globale Singleton-Verbindung | -| **FLAW-04** | Datenbank / Suche | Volltextsuche via `LIKE '%query%'` statt SQLite FTS5 | **Mittel** | Performance-EinbrĂŒche bei wachsenden NotizbestĂ€nden (>10.000 Blöcke) | -| **FLAW-05** | Frontend DOM | Fehlende Virtualisierung im Outliner-Baum | **Mittel** | Frame-Drops und Lags bei Dokumenten mit >500 Blöcken auf MobilgerĂ€ten | -| **FLAW-06** | SQLite Concurrency | Single-Writer-Bottleneck bei hohem parallelem Schreibaufkommen | **Mittel** | `SQLITE_BUSY` Fehler bei vielen gleichzeitigen Team-Nutzern | -| **FLAW-07** | Benachrichtigungen | Fehlende Dead-Letter-Queue fĂŒr Webhook- & E-Mail-Alarme | **Niedrig** | Verlorene Benachrichtigungen bei temporĂ€rem Ausfall von Drittsystemen | -| **FLAW-08** | Sicherheit / Auth | Statische JWT-Tokens ohne Blacklisting & Token-Versionierung | **Gelöst (v2.2)** | Behoben durch `token_version`-Spalte in `users`, `revoked_tokens`-Sperrliste und sofortige Sitzungsinvalidierung bei PasswortĂ€nderung/Logout/Rechteentzug | +| ID | Bereich | Kurzbeschreibung | Schwere | +|---|---|---|---| +| FLAW-01 | Realtime | `BroadcastToAll` sendet Aufgaben- und Listendaten an alle Verbindungen, auch unauthentifizierte | **Hoch** | +| FLAW-02 | Realtime | RĂ€ume (WS `JOIN_ROOM`, SSE `/events/rooms`, `?listId=`) ohne BerechtigungsprĂŒfung | **Hoch** | +| FLAW-03 | Aufgaben | `ReorderTasks` Ă€ndert beliebige Aufgaben per ID, ohne ZugriffsprĂŒfung (IDOR) | **Hoch** | +| FLAW-04 | Auth | Fest eincodiertes Fallback-`JWT_SECRET` | **Hoch** | +| FLAW-05 | Benachrichtigungen | Discord-/Webhook-URLs ungeprĂŒft (SSRF) | Mittel | +| FLAW-06 | Aufgaben | Jeder Editor einer Aufgabenliste kann sie per `teamId` an weitere Gruppen freigeben | Mittel | +| FLAW-07 | Auth | API-Key-`scopes` werden nie ausgewertet; ein Key ist ein Vollzugriff | Mittel | +| FLAW-08 | Auth | Webhook-Ingest mit JWT prĂŒft weder Sperrliste noch `token_version` noch `is_active` | Mittel | +| FLAW-09 | Auth | Kein Rate-Limit, keine Sperre bei Fehlversuchen, Mindestpasswort 4 Zeichen | Mittel | +| FLAW-10 | Freigaben | `sharing_mode` wird bei Freigaben nicht durchgesetzt; Benutzerverzeichnis leakt alle Konten | Mittel | +| FLAW-11 | Workspace | Block-Upsert (`ON CONFLICT(id)`) prĂŒft das Dokument nicht | Mittel | +| FLAW-12 | Benachrichtigungen | `notify_before_minutes` wirkungslos; Alarm feuert nach Umplanung nie wieder | Mittel | +| FLAW-13 | Datenbank | Keine Schema-Versionierung; Migrationsfehler werden verschluckt; `migrations/001_init.sql` ist toter Code | Mittel | +| FLAW-14 | Backend | Fehler von Schreiboperationen werden hĂ€ufig ignoriert (`_, _ =`) | Mittel | +| FLAW-15 | Realtime | `WriteTimeout` von 30 s trifft vermutlich auch SSE-Streams *(nicht live verifiziert)* | Mittel | +| FLAW-16 | Setup | `/auth/setup` ist bis zum ersten Konto offen; Demo-Modus legt `admin/admin` an | Mittel | +| FLAW-17 | Workspace | Dokument- und Ordnerfreigaben sind per Migration erzwungen schreibgeschĂŒtzt; tote Schreibpfade | Niedrig | +| FLAW-18 | Suche | Nur eigene Gedanken und Dokumente, `LIKE` ohne Escaping, keine FTS | Niedrig | +| FLAW-19 | QualitĂ€t | Keine Frontend-Tests, `lint` und `test:run` sind Platzhalter | Niedrig | +| FLAW-20 | QualitĂ€t | Sehr große Dateien, duplizierte Zugriffslogik | Niedrig | +| FLAW-21 | Repo | `LICENSE` fehlt, obwohl README MIT angibt | Niedrig | +| FLAW-22 | Netzwerk | Token in der URL (WS/SSE), CORS `*`, `CheckOrigin` immer `true` | Niedrig | +| FLAW-23 | SQLite | Single-Writer-Engpass bei vielen gleichzeitigen Schreibern | Niedrig | +| FLAW-24 | Gruppen | Organisations-Endpunkte ohne BerechtigungsprĂŒfung | Mittel | --- -## 🔍 Detaillierte Analyse & Maßnahmen +## Details -### FLAW-01: [GELÖST / IMPLEMENTIERT] Granulare Outliner-Synchronisation & Multi-User Kollaboration -- **Status:** **Gelöst via In-Memory DocRooms, Echo-Schutz & Debounced Persistence** -- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), [`modules/workspace.md`](./modules/workspace.md), `internal/ws/docroom.go`, `internal/storage/sqlite.go`, `frontend/src/lib/useDocumentSync.ts`, `frontend/src/features/workspace/OutlinerBlock.tsx` -- **Ursache (Historisch):** MindSift nutzte frĂŒher pessimistisches Dokumentensperren (Lease-Locks mit Heartbeats). Bei parallelem Bearbeiten blockierten sich Nutzer gegenseitig; unausgereifte Sync-Versuche fĂŒhrten zu Selbstechos und Cursor-Jumps. -- **Implementierte Lösung:** - 1. **In-Memory DocRooms:** Granularer WebSocket-Raum pro Dokument (`/api/v1/ws/docs/:id`) mit Mutex-geschĂŒtztem Block-State im Go-Backend. - 2. **Strikter Echo-Schutz per `clientId`:** Jede Verbindung besitzt eine eindeutige `clientId`. Der Hub filtert den Sender heraus und leitet Events nur an Peers weiter. - 3. **Live Presence & Ephemere Cursors:** Cursorposition und Textselektionen (`cursor_moved`) werden flĂŒchtig an Peers gestreamt, ohne SQLite zu belasten. - 4. **Fokus- & Eingabeschutz (`OutlinerBlock.tsx`):** Remote-Updates werden nur blockiert, wenn `document.hasFocus()` vorliegt. Bei `window.onblur` und `input.onblur` wird sofort (0ms) geflusht; Callbacks werden ĂŒber `useRef` gefĂŒhrt (keine Stale Closures). - 5. **1.5s InaktivitĂ€ts-Debounce-Flush:** Nach 1,5s InaktivitĂ€t sichert der Raum persistent in SQLite (`blocks`, Regex `#tags` in `block_tags` und kompiliertes Markdown in `documents.markdown_cache`). - 6. **VollstĂ€ndige Eliminierung von Locks:** Alle blockierenden Sperren wurden restlos entfernt. Mehrere Nutzer können zeitgleich schreiben. +### FLAW-01: Broadcast an alle Verbindungen (Datenabfluss) +- **Fundstellen:** [`internal/ws/hub.go`](../internal/ws/hub.go) `BroadcastToAll`; Aufrufer in [`internal/api/server.go`](../internal/api/server.go): `handleUpdateThought` (`TASK_UPDATED`), `handleDeleteThought` (`TASK_DELETED`), `handleReorderTasks` (`TASK_REORDERED`), `handleUpdateTaskList`, `handleCreateTaskListShare`, `handleDeleteTaskListShare` (`TASK_LIST_UPDATED`), `handleDeleteTaskList`. +- **Problem:** `BroadcastToAll` schreibt an `allClients` und `sseClients`. Dazu zĂ€hlen auch Verbindungen mit `userID = "anonymous"`, denn `handleWebSocket` und `handleSSE` liegen außerhalb der Auth-Gruppe und lassen jede Verbindung zu. Der vollstĂ€ndige Aufgabeninhalt (Titel, Notizen, Unteraufgaben) und die Listen samt Mitgliedern gehen so an jeden, der `/api/v1/events` öffnet. +- **Maßnahme:** Ereignisse an die berechtigten Nutzer adressieren (wie bei `DOC_*` ĂŒber `GetAuthorizedDocUsers`), unauthentifizierte Verbindungen ablehnen. + +### FLAW-02: RĂ€ume ohne BerechtigungsprĂŒfung +- **Fundstellen:** `Hub.readPump` (`JOIN_ROOM`), `handleSSERooms` (Route `/api/v1/events/rooms` liegt **außerhalb** der Auth-Gruppe), `Hub.Register` (Raum aus `?listId=`/`?spaceId=`). +- **Problem:** Wer eine Raum-ID kennt (`doc:`, Listen-ID), tritt bei und erhĂ€lt Inhalte wie `DOC_BLOCKS_PATCHED` oder `ITEM_*`. Raum-IDs tauchen in Ereignissen auf und sind nicht geheim. FĂŒr den SSE-Beitritt reicht die `sseId`, die der Server selbst im Stream mitsendet. +- **Maßnahme:** Beitritt nur nach ZugriffsprĂŒfung (`GetDocumentByID`, `GetListByID`); `/events/rooms` hinter `authMiddleware`. + +### FLAW-03: `ReorderTasks` ohne ZugriffsprĂŒfung (IDOR) +- **Fundstelle:** `Store.ReorderTasks` (`UPDATE thoughts SET sort_order 
 WHERE id = ?`) und `handleReorderTasks`. +- **Problem:** Die SQL-Anweisung ist weder auf `user_id` noch auf die Liste eingeschrĂ€nkt. Jeder angemeldete Nutzer kann `sortOrder`, `kanbanStatus` und damit `isCompleted` fremder Aufgaben Ă€ndern, sofern er die ID kennt. Die Leser-PrĂŒfung fĂŒr Aufgabenlisten ĂŒberspringt Aufgaben, auf die der Nutzer gar keinen Zugriff hat (`GetThoughtByID` liefert dann einen Fehler), und schĂŒtzt davor nicht. +- **Maßnahme:** Jede ID gegen `accessibleTaskListIDsQuery` bzw. `user_id` prĂŒfen und bei Verstoß ablehnen. + +### FLAW-04: Fallback-JWT-Secret +- **Fundstelle:** [`internal/config/config.go`](../internal/config/config.go): `getEnv("JWT_SECRET", "mindsift-default-selfhosted-jwt-secret-key-2026")`; `docker-compose.yml` und `.env.example` liefern ebenfalls einen Platzhalter. +- **Problem:** Wer `JWT_SECRET` nicht setzt (oder den Platzhalter behĂ€lt), lĂ€uft mit einem öffentlich bekannten SchlĂŒssel. Damit lassen sich gĂŒltige Tokens fĂŒr jede Benutzer-ID fĂ€lschen. `install.sh` erzeugt zwar ein Secret, andere Installationswege nicht. +- **Maßnahme:** Start verweigern oder Secret beim ersten Start erzeugen und in `/data` ablegen. + +### FLAW-05: SSRF ĂŒber Alarm-URLs +- **Fundstellen:** `handleUpdateUserSettings` (keine Validierung), `sendDiscordNotification`, `sendCustomWebhookNotification`. +- **Problem:** Jeder Nutzer kann beliebige URLs hinterlegen. Der Server sendet POST-Anfragen dorthin, auch an interne Adressen (`http://169.254.169.254`, Docker-Netz, `localhost`). Über `handleTestUserAlarm` lĂ€sst sich das sofort auslösen. +- **Maßnahme:** Nur `https`, Discord-Host prĂŒfen, private/Loopback-Adressen beim Verbinden sperren. + +### FLAW-06: Editor kann Aufgabenliste weiterteilen +- **Fundstelle:** `Store.UpdateTaskList` (`teamID`-Zweig ruft `AddTaskListShare`); `handleUpdateTaskList` prĂŒft nur, dass der Nutzer **kein** Leser ist. +- **Problem:** `POST /task-lists/{id}/shares` ist EigentĂŒmern vorbehalten. Über `PUT /task-lists/{id}` mit `teamId` kann aber jeder Editor die Liste einer weiteren Gruppe zuweisen (mit Schreibrecht) und die Zuweisung der bisherigen Gruppe aufheben. +- **Maßnahme:** `teamId`-Änderungen nur fĂŒr den EigentĂŒmer; das Legacy-Feld `task_lists.team_id` zugunsten von `task_list_shares` abschaffen. + +### FLAW-07: API-Key-Scopes ohne Wirkung +- **Fundstellen:** Tabelle `api_keys.scopes` (Standard `ingest,read`), `authMiddleware`. +- **Problem:** Ein Key mit PrĂ€fix `mindsift_sec_` authentifiziert als der Benutzer, mit allen Rechten: alle `/api/v1`-Routen, bei Admins auch `/admin/*`, außerdem das Anlegen weiterer Keys. FĂŒr Webhooks (nur Aufgaben anlegen) ist das ĂŒberdimensioniert. +- **Maßnahme:** Scopes auswerten; Keys standardmĂ€ĂŸig nur auf `/inbox` und `/webhooks/ingest` beschrĂ€nken. + +### FLAW-08: Webhook-Ingest umgeht SitzungsprĂŒfung +- **Fundstelle:** `handleWebhookIngest`, JWT-Zweig. +- **Problem:** Das Token wird nur kryptografisch validiert. `IsTokenRevoked`, `token_version` und `is_active` werden nicht geprĂŒft; ein ausgeloggtes oder deaktiviertes Konto kann weiter Aufgaben anlegen (bis Token-Ablauf nach 30 Tagen). +- **Maßnahme:** Dieselbe PrĂŒfkette wie in `authMiddleware` verwenden (gemeinsame Funktion statt Kopien; siehe auch `authenticateUserFromReq`). + +### FLAW-09: Kein Schutz gegen Brute-Force +- **Fundstellen:** `handleLogin`, `handleRegister`, `handleFirstBootSetup`. +- **Problem:** Weder Rate-Limit noch Sperre; `middleware.RealIP` vertraut `X-Forwarded-For` ungeprĂŒft. MindestpasswortlĂ€nge ist 4 Zeichen. Der Login-Pfad ist so ĂŒber das Internet raten- und durchprobierbar. +- **Maßnahme:** Rate-Limit je IP und Konto, lĂ€ngeres Mindestpasswort. + +### FLAW-10: `sharing_mode` wird nicht durchgesetzt +- **Fundstellen:** `Store.GetDiscoverableUsers`, `Store.GetCollaborators`; Share-Handler in `server.go`. +- **Problem:** + 1. Der Modus beeinflusst nur `GetDiscoverableUsers`. In `teams_only` liefert die Funktion trotzdem **alle** Benutzer (`if 
 mode == "open" || mode == "teams_only" { return GetAllUsers() }`). + 2. `GET /collaborators` ignoriert den Modus komplett und liefert alle aktiven Konten mit E-Mail-Adresse. + 3. Keine der Share-Funktionen prĂŒft den Modus. `strict_private` und `teams_only` verhindern Freigaben nicht, wenn man die Ziel-ID kennt. +- **Maßnahme:** Eine zentrale Funktion `canShareWith(owner, target)` und in allen Share-Handlern verwenden; Verzeichnisse darauf einschrĂ€nken. + +### FLAW-11: Block-Upsert prĂŒft das Dokument nicht +- **Fundstelle:** `Store.PatchDocumentBlocks`, `INSERT INTO blocks 
 ON CONFLICT(id) DO UPDATE`. +- **Problem:** `blocks.id` ist global eindeutig. Existiert die ID bereits in einem **anderen** Dokument, ĂŒberschreibt der Konflikt-Zweig dessen Inhalt, ohne `document_id` zu prĂŒfen. Voraussetzung: Schreibrecht im eigenen Dokument und Kenntnis einer fremden Block-ID. +- **Maßnahme:** `DO UPDATE 
 WHERE blocks.document_id = excluded.document_id`; fremde IDs ablehnen. + +### FLAW-12: Alarm-Logik +- **Fundstellen:** `startAlarmTicker`, `GetDueTasksForAlarm`, `UpdateThoughtDetails`, `UserSettingsView`. +- **Probleme:** + - `notify_before_minutes` (Einstellungen: „15 Minuten vorher“ usw.) wird nirgends gelesen. FĂ€lligkeiten haben nur ein Datum (`YYYY-MM-DD`), eine Vorlaufzeit ist damit nicht abbildbar. + - Verglichen wird mit `time.Now()` in der Zeitzone des Containers (ohne `TZ` also UTC). + - `alarm_sent` wird nie zurĂŒckgesetzt. Wird die FĂ€lligkeit nach einem Alarm verschoben, gibt es keinen neuen. + - Schlagen alle KanĂ€le fehl, wird jede Minute erneut gesendet (kein Backoff, keine Dead-Letter-Queue). +- **Maßnahme:** FĂ€lligkeit mit Uhrzeit speichern, `alarm_sent` bei Änderung von `due_date` zurĂŒcksetzen, Wiederholungen begrenzen. + +### FLAW-13: Migrationen +- **Fundstellen:** `Store.migrate` in [`internal/storage/sqlite.go`](../internal/storage/sqlite.go), [`migrations/001_init.sql`](../migrations/001_init.sql). +- **Probleme:** + - Es gibt keine Versionstabelle. Alle `ALTER TABLE`-Anweisungen laufen bei jedem Start erneut; Fehler werden pauschal ignoriert (`_, _ = s.db.Exec(q)`), echte Fehler fallen nicht auf. + - Mehrere `UPDATE`/`DROP TABLE`-Bereinigungen laufen bei jedem Start. + - `migrations/001_init.sql` wird nur in das Image kopiert, aber nirgends gelesen, und ist gegenĂŒber dem tatsĂ€chlichen Schema veraltet. +- **Maßnahme:** `schema_migrations`-Tabelle mit nummerierten, einmalig ausgefĂŒhrten Schritten; die SQL-Datei entweder einbetten oder entfernen. + +### FLAW-14: Verschluckte Fehler +- **Fundstellen:** u. a. `PatchDocumentBlocks` (Upserts/Deletes), `AddTaskListShare`, `RemoveTaskListShare`, viele `_, _ = s.db.Exec`. +- **Problem:** Scheitert ein Schreibzugriff (z. B. `SQLITE_BUSY`), antwortet die API trotzdem mit Erfolg und broadcastet den Stand, der nie gespeichert wurde. Fehler lassen sich nur schwer nachvollziehen. +- **Maßnahme:** Mehrstufige Änderungen in Transaktionen bĂŒndeln und Fehler zurĂŒckgeben. + +### FLAW-15: WriteTimeout und SSE *(nicht live verifiziert)* +- **Fundstelle:** [`cmd/server/main.go`](../cmd/server/main.go) `WriteTimeout: 30 * time.Second`. +- **Problem:** Go beendet nach diesem Zeitraum das Schreiben der Antwort. FĂŒr den SSE-Fallback (`ServeSSE`) bedeutet das voraussichtlich einen Verbindungsabbruch alle 30 s. WebSockets sind durch Hijacking nicht betroffen. Der Client verbindet sich neu, daher fĂ€llt es kaum auf. +- **Maßnahme:** Im SSE-Handler `http.ResponseController.SetWriteDeadline(time.Time{})` setzen oder `WriteTimeout` fĂŒr diese Route aufheben. + +### FLAW-16: Ersteinrichtung und Demo-Modus +- **Fundstellen:** `handleFirstBootSetup`, `Store.EnsureDemoAccounts`, `config.Load` (`MINDA_DEMO`, `MINDSIFT_DEMO`, `DEMO_MODE`). +- **Probleme:** Solange kein Konto existiert, kann jeder `POST /auth/setup` aufrufen und wird Administrator. Mit gesetztem Demo-Flag entstehen Konten `admin/admin` und `user/user`; versehentlich auf einer öffentlichen Instanz aktiv wĂ€re das eine KomplettĂŒbernahme. +- **Maßnahme:** Setup per einmaligem Token aus dem Server-Log absichern; Demo-Modus nur zusammen mit expliziter Warnung/BestĂ€tigung. + +### FLAW-17: SchreibgeschĂŒtzte Dokumentfreigaben +- **Fundstellen:** Migration in `Store.migrate` (`UPDATE doc_shares SET permission = 'read' 
`, ebenso `doc_team_shares`, `workspace_folder_shares`), `PatchDocumentBlocks`/`UpdateDocument` (nur EigentĂŒmer). +- **Hinweis:** Das ist offenbar gewollt: Nur der EigentĂŒmer schreibt in ein Dokument, EmpfĂ€nger lesen. Es gibt daher **kein** kollaboratives Bearbeiten. Das Schema (`CHECK(permission IN ('read','write'))`), die Zweige fĂŒr `write` im Code und frĂŒhere Dokumentation suggerieren etwas anderes. Es gibt weder Sperren noch Konfliktauflösung, noch Cursor-Presence. +- **Maßnahme:** Entweder Schreibfreigaben mit Konfliktbehandlung ergĂ€nzen oder den toten Code und das Schema bereinigen. + +### FLAW-18: Suche +- **Fundstelle:** `Store.SearchAll`. +- **Probleme:** Durchsucht nur eigene Gedanken (30) und eigene Dokumente (20); geteilte Dokumente, Aufgaben aus geteilten Listen, Einkaufslisten und Blöcke bleiben außen vor. `LIKE '%
%'` ohne Escaping von `%`/`_`, keine Indizes, keine Relevanz. +- **Maßnahme:** FTS5-Index ĂŒber Blöcke/Gedanken; Berechtigungen mitberĂŒcksichtigen. + +### FLAW-19: Keine automatisierten Frontend-PrĂŒfungen +- **Fundstelle:** [`frontend/package.json`](../frontend/package.json): `"lint": "echo 'Linting passed'"`, `"test:run": "echo 'Frontend checks passed'"`. +- **Problem:** Die Skripte melden immer Erfolg. Die CI (`.gitea/workflows/build-and-publish.yaml`) prĂŒft daher nur Go-Tests und den Vite-Build. Regressionen im UI (z. B. Berechtigungsanzeigen) fallen nicht auf. +- **Maßnahme:** `tsc --noEmit` und ESLint als echte Skripte, Komponententests fĂŒr kritische Pfade. + +### FLAW-20: DateigrĂ¶ĂŸe und Duplikate +- `internal/storage/sqlite.go` (≈ 5.600 Zeilen), `internal/api/server.go` (≈ 3.000), `WorkspaceView.tsx` (≈ 2.300), `TasksView.tsx` (≈ 2.500), `api.ts` (≈ 1.500). +- Die Zugriffsabfrage fĂŒr Aufgabenlisten steht dreimal fast identisch im Code (`accessibleTaskListIDsQuery`, `GetTaskLists`, `GetTaskListByID`). Dokumentzugriff ist Ă€hnlich verteilt. Änderungen an Berechtigungen mĂŒssen an mehreren Stellen erfolgen und werden leicht vergessen. +- **Maßnahme:** In Teilmodule zerlegen (Auth, Dokumente, Aufgaben, Listen, Teams), ZugriffsprĂŒfung zentralisieren. + +### FLAW-21: Lizenzdatei fehlt +- README und Badge nennen die MIT-Lizenz und verlinken auf `LICENSE`; die Datei existiert nicht im Repository. + +### FLAW-22: Netzwerk-Details +- `authenticateUserFromReq` akzeptiert das Token als `?token=` (nötig fĂŒr `WebSocket`/`EventSource`, die keine Header setzen). Tokens landen dadurch in Proxy- und Zugriffslogs. +- `AllowedOrigins: ["*"]` (Auth lĂ€uft ĂŒber `Authorization`-Header, nicht ĂŒber Cookies, das Risiko ist daher begrenzt) und `CheckOrigin` liefert immer `true`. +- Ein einmal verbundener WebSocket/SSE-Stream wird bei Logout oder Token-Widerruf nicht getrennt. +- **Maßnahme:** Kurzlebiges, einmaliges Stream-Ticket statt Langzeit-JWT in der URL; Origin gegen `BASE_URL` prĂŒfen. + +### FLAW-23: SQLite-Schreibengpass +- Eine SQLite-Datei (WAL, `busy_timeout`, bis zu 8 offene Verbindungen) erlaubt nur einen gleichzeitigen Schreiber. FĂŒr Familien und kleine Teams unkritisch; bei vielen aktiven Editoren sind `SQLITE_BUSY`-Fehler möglich, die wegen FLAW-14 stumm bleiben. + +### FLAW-24: Organisations-Endpunkte ohne BerechtigungsprĂŒfung +- **Fundstellen:** `handleDeleteOrganization`, `handleGetOrgTeams`, `handleCreateOrgTeam` (und `Store.DeleteOrganization`) in `server.go` bzw. `sqlite.go`. +- **Problem:** Diese Handler prĂŒfen weder EigentĂŒmer noch Mitgliedschaft. Jeder angemeldete Nutzer kann mit bekannter ID eine fremde Organisation löschen, deren Gruppen auflisten oder dort Gruppen anlegen. Gruppenbezogene Endpunkte (`/teams/{id}
`) prĂŒfen dagegen korrekt auf Gruppen-Admin. Das Datenmodell kennt Organisationen nur noch rudimentĂ€r; im UI werden Gruppen direkt ĂŒber `POST /teams` angelegt (`orgId` Standard `default`). +- **Maßnahme:** Organisationen entweder konsequent absichern (EigentĂŒmer/Server-Admin) oder entfernen. --- -### FLAW-02: [OBSOLET / GELÖST] Dual-Storage Drift (SQLite vs. Dateisystem) -- **Status:** **EntkrĂ€ftet & Behoben (Pure Database Architecture)** -- **Klarstellung:** In frĂŒheren EntwĂŒrfen wurde angedacht, Notizen zusĂ€tzlich als Markdown-Dateien auf dem Dateisystem zu spiegeln. MindSift speichert Notizen und Blöcke jedoch **zu 100% relational in SQLite** (`documents` und `document_blocks`). -- **Verifikation:** - - Es existieren **keine** Markdown-Dateien auf der Festplatte. - - Das Go-Backend schreibt keine Dateien in ein Notizverzeichnis. - - Unnötige Verzeichniserstellungen (`docDir`/`workspace`) wurden aus `internal/storage/sqlite.go` vollstĂ€ndig entfernt. -- **Ergebnis:** Es gibt keinen Dual-Storage und somit keinen File-Drift oder externe Dateikonflikte. Notizen verhalten sich vollstĂ€ndig transaktional und konsistent innerhalb der SQLite-Datenbank. +## Aus der frĂŒheren Fassung entfernt ---- - -### FLAW-03: [GELÖST / IMPLEMENTIERT] Proxy-InkompatibilitĂ€t bei WebSocket-Upgrades & Polling-Fallback -- **Status:** **Gelöst & VollstĂ€ndig Implementiert (Dual-Transport SSE Fallback & Proxy-Resilienz)** -- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), [`reverse-proxy.md`](./reverse-proxy.md), [`internal/ws/hub.go`](../internal/ws/hub.go), [`internal/api/server.go`](../internal/api/server.go), [`frontend/src/lib/api.ts`](../frontend/src/lib/api.ts) -- **Problemursache:** Corporate Proxies (z.B. Zscaler, BlueCoat, strikte Firewalls) oder unvollstĂ€ndig konfigurierte Reverse-Proxies verwerfen den HTTP-Upgrade-Header `Upgrade: websocket` (`NS_ERROR_CONNECTION_REFUSED`, Status 1006 / 1002). FrĂŒher fiel der Client dabei auf 2-Sekunden-Polling zurĂŒck, was zu Verzögerungen und Last fĂŒhrte. -- **Implementierte Lösung:** - 1. **Unidirektionaler Server-Sent Events (SSE) Fallback:** - - Neuer Streaming-Endpunkt `GET /api/v1/events` im Go-Backend mit `Content-Type: text/event-stream`, `X-Accel-Buffering: no` und 15s Heartbeat-Pings (`: ping\n\n`). - - Standard HTTPS Ports (443/80), wodurch kein Proxy den Datenstrom blockieren kann. - 2. **Hub-Multiplexing:** - - `ws.Hub` verwaltet WebSocket- und SSE-Clients parallel in gemeinsamen RĂ€umen (`rooms`, `userRooms`, `allClients`). - - SĂ€mtliche Events (`BroadcastToAll`, `BroadcastToUser`, `BroadcastRoom`) werden transparent an beide Transportarten verteilt. - 3. **REST-ParitĂ€t fĂŒr Outbound CRDT & Awareness:** - - `PUT /api/v1/documents/:id/crdt`: Akzeptiert Delta-Updates (`update`) und broadcastet sie an alle Peers. - - `POST /api/v1/documents/:id/awareness` und `DELETE /api/v1/documents/:id/awareness`: Übertragen Blockfokus und Cursor ohne WebSocket. - - `POST /api/v1/events/rooms`: Erlaubt SSE-Clients dynamisches Room-Joining. - 4. **Frontend Realtime Transport Manager (`frontend/src/lib/api.ts`):** - - Automatischer 3,5s-Watchdog: SchlĂ€gt das WebSocket-Upgrade fehl oder wird Code 1006/1002 gemeldet, wechselt MindSift nahtlos auf `EventSource('/api/v1/events')`. - - GrĂŒner/tĂŒrkiser Statusindikator im Header zeigt `Live (WebSocket)` bzw. `Live (Server-Sent Events / Proxy-Stream)`. - 5. **Reverse-Proxy-Handbuch:** - - VollstĂ€ndige Produktions-Konfigurationen fĂŒr Nginx, Traefik, Caddy, Cloudflare und Apache unter [`reverse-proxy.md`](./reverse-proxy.md). - ---- - -### FLAW-04: Volltextsuche via `LIKE '%query%'` -- **Betroffene Komponenten:** [`database.md`](./database.md), `internal/api/search.go`, `internal/api/thoughts.go` -- **Ursache:** Suchabfragen ĂŒber Gedanken, Notizblöcke und Aufgaben nutzen aktuell einfaches SQL: - ```sql - WHERE content LIKE '%' || ? || '%' - ``` -- **Folgen:** `LIKE '%...'` kann B-Tree-Indizes prinzipbedingt nicht nutzen und erzwingt einen sequentiellen Full Table Scan. Bei Datenbanken mit Zehntausenden Blöcken fĂŒhrt dies zu CPU-Spitzen und trĂ€gen Suchreaktionen. -- **Empfohlene Lösung:** - 1. Einrichten einer virtuellen SQLite FTS5-Tabelle (`document_blocks_fts`) mit Tokenizer (`unicode61` oder `trigram`). - 2. Automatische Synchronisierung ĂŒber SQLite Trigger (`AFTER INSERT`, `AFTER UPDATE`, `AFTER DELETE`). - ---- - -### FLAW-05: Fehlende Virtualisierung im Outliner-Baum -- **Betroffene Komponenten:** [`modules/workspace.md`](./modules/workspace.md), `frontend/src/features/workspace/WorkspaceView.tsx` -- **Ursache:** Jeder Block im Notizbaum wird als eigenes DOM-Element gerendert. EnthĂ€lt ein Dokument 500 bis 2000 Blöcke (z. B. lange Projektjournale oder Besprechungsprotokolle), befinden sich tausende verschachtelte `div`- und `contenteditable`-Elemente gleichzeitig im DOM. -- **Folgen:** Auf Ă€lteren Smartphones oder bei schnellem Tippen kommt es zu spĂŒrbaren Input-Latenzen und Ruckeln beim Scrollen. -- **Empfohlene Lösung:** - 1. Implementierung einer virtuellen Fensterung (Virtual Windowing) fĂŒr Blöcke außerhalb des Sichtbereichs. - 2. Automatisches Einklappen (*Collapsing*) tiefer Hierarchieebenen beim initialen Laden langer Dokumente. - ---- - -### FLAW-06: SQLite Concurrency & Single-Writer-Bottleneck -- **Betroffene Komponenten:** [`architecture.md`](./architecture.md), [`database.md`](./database.md), `internal/storage/sqlite.go` -- **Ursache:** Obwohl SQLite mit `PRAGMA journal_mode = WAL;` parallele Leser erlaubt, kann zu jedem Zeitpunkt immer nur **ein einziger Thread** schreiben. Bei intensiver gleichzeitiger Nutzung (z. B. 20 Teammitglieder tippen gleichzeitig in Listen und Notizen, wĂ€hrend im Hintergrund Webhook-Events einlaufen) können Schreib-Transaktionen aufeinander warten. -- **Folgen:** Risiko von `database is locked (SQLITE_BUSY)`-Fehlern, wenn der Busy-Timeout ĂŒberschritten wird. -- **Empfohlene Lösung:** - 1. Setzen von `PRAGMA busy_timeout = 5000;` (5 Sekunden Wartezeit vor Fehlerwurf). - 2. Kapselung aller Schreibzugriffe ĂŒber einen dedizierten Go-Channel / Single-Worker-Pool oder Migration auf PostgreSQL bei grĂ¶ĂŸeren Installationen. - ---- - -### FLAW-07: Fehlende Dead-Letter-Queue fĂŒr Benachrichtigungen -- **Betroffene Komponenten:** [`modules/tasks.md`](./modules/tasks.md), `internal/scheduler/alarm_ticker.go` -- **Ursache:** Der Alarm-Ticker ĂŒberprĂŒft minĂŒtlich fĂ€llige Aufgaben und sendet Webhooks bzw. E-Mails. SchlĂ€gt der HTTP-Post an den externen Webhook fehl (z. B. Zielserver vorĂŒbergehend offline), wird der Fehler lediglich ins Log geschrieben. -- **Folgen:** Der Alarm wird nicht erneut versucht und die Benachrichtigung geht dauerhaft verloren. -- **Empfohlene Lösung:** - 1. EinfĂŒhrung einer Tabelle `notification_outbox` mit Spalten `retry_count`, `next_retry_at`, `status`. - 2. Exponentieller Backoff (1m, 5m, 15m, 1h) und Status-Markierung (*failed*, *delivered*). - ---- - -### FLAW-08: [GELÖST / IMPLEMENTIERT] JWT-Sicherheit, Token-Blacklisting & Token-Versionierung -- **Status:** **Gelöst via `token_version`-Spalte in `users`, `revoked_tokens`-Blacklist und serverseitige Invalidierungs-Endpunkte** -- **Betroffene Komponenten:** [`architecture.md`](./architecture.md), [`database.md`](./database.md), `internal/api/server.go`, `internal/auth/auth.go`, `internal/storage/sqlite.go`, `frontend/src/lib/api.ts` -- **Ursache (Historisch):** Nach dem Login wurde ein statisches JWT ausgestellt, das lediglich kryptografisch signiert war. Bei PasswortĂ€nderungen, RechteĂ€nderungen oder Konto-Deaktivierung blieben bereits ausgestellte Tokens bis zum Ablaufdatum gĂŒltig. -- **Implementierte Lösung:** - 1. **`token_version`-Spalte in `users`:** Jedes ausgestellte JWT fĂŒhrt die `tokenVersion` des Benutzers im Claim. Bei einer PasswortĂ€nderung (`UpdateUserPassword`), RechterĂŒckstufung oder Deaktivierung wird `token_version = token_version + 1` atomar hochgezĂ€hlt. Alle alten Tokens auf beliebigen GerĂ€ten werden sofort mit `401 Unauthorized` abgewiesen. - 2. **Tabelle `revoked_tokens` fĂŒr Einzelsitzungen:** Beim Klick auf Logout (`POST /api/v1/auth/logout`) wird das spezifische JWT via SHA-256-Hash in die Sperrliste eingetragen, bis sein regulĂ€res Ablaufdatum erreicht ist. - 3. **"Alle Sitzungen beenden" (`POST /api/v1/auth/revoke-all`):** Ermöglicht es dem Benutzer in den Einstellungen ("Profil & Sicherheit") oder einem Administrator, mit einem Klick alle aktiven Sitzungen auf sĂ€mtlichen Browsern und MobilgerĂ€ten zu terminieren. - 4. **Konsistente PrĂŒfung bei WebSockets & SSE:** Auch bei Echtzeit-Verbindungen (`/api/v1/ws`, `/api/v1/ws/docs/:id`, `/api/v1/events`) wird der Benutzerstatus, die Sperrliste und die aktuelle `token_version` ĂŒberprĂŒft. Outdated oder gesperrte Clients werden sofort abgewiesen. - ---- - -## 🧭 Verwandte Dokumente -- đŸ—ïž [Gesamtarchitektur](architecture.md) -- đŸ’Ÿ [Datenbank-Design & Tabellenschemata](database.md) -- 🔌 [WebSocket & Live-Protokoll](api/websocket.md) -- 🧠 [Workspace-Modul (Outliner)](modules/workspace.md) -- đŸ›Ąïž [Rechte & Freigabesystem](sharing-and-permissions.md) +Die frĂŒhere `flaws.md` fĂŒhrte als „gelöst“, was im Code nicht existiert: In-Memory-DocRooms, `clientId`-Echo-Schutz, Cursor-Presence, 1,5-s-Debounce-Flush, `internal/ws/docroom.go`, `frontend/src/lib/useDocumentSync.ts` und Yjs/CRDT. Der Code enthĂ€lt nur noch `internal/ws/hub.go` (RĂ€ume, Benutzer-KanĂ€le, SSE-Fallback). Die CRDT-Tabellen werden beim Start gelöscht (`DROP TABLE document_crdt_*`). Dokumente werden per REST gespeichert (`PATCH /documents/{id}/blocks`) und als `DOC_BLOCKS_PATCHED` verteilt. Die frĂŒhere Aussage zur Token-Versionierung (`token_version`, `revoked_tokens`) trifft dagegen zu und ist in [sharing-and-permissions.md](sharing-and-permissions.md) und [architecture.md](architecture.md) beschrieben. diff --git a/Doku/modules/lists.md b/Doku/modules/lists.md index 26c57dd..be17af6 100644 --- a/Doku/modules/lists.md +++ b/Doku/modules/lists.md @@ -1,62 +1,19 @@ -# 🛒 Kernmodul 4: Geteilte Listen & Kollaboration +# Modul: Geteilte Listen -Das Modul fĂŒr geteilte Listen ([`SharedListsView.tsx`](../../frontend/src/features/lists/SharedListsView.tsx)) ist auf **Echtzeit-Kollaboration im Alltag** ausgelegt (z.B. gemeinsamer Wocheneinkauf im Supermarkt, Packlisten fĂŒr Reisen, Inventarlisten oder schnelle Checklisten). +Einkaufs-, Pack- und Checklisten fĂŒr gemeinsames Arbeiten. Code: [SharedListsView.tsx](../../frontend/src/features/lists/SharedListsView.tsx); Backend: `*List*`-Handler in [server.go](../../internal/api/server.go), `Store.CreateList` und Verwandte. ---- +## Datenmodell -## đŸ›ïž 1. Listen-Typen & Kategorien +- `lists`: EigentĂŒmer, `title`, `icon`, `list_type` (`shopping`, `checklist`, `vacation`). +- `list_items`: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `sort_order`. +- `list_shares`: Ziel (Benutzer oder Gruppe) und Rolle `editor` oder `viewer`. -Jede Liste besitzt einen Typ, ein Icon und eine Farb-Codierung: +## Funktionen -| Listen-Typ | Icon | Einsatzzweck | Typische Kategorien | -| :--- | :--- | :--- | :--- | -| `shopping` | 🛒 | Supermarkt- & Lebensmitteleinkauf | Obst & GemĂŒse, KĂŒhlregal, Vorrat, Drogerie | -| `packing` | 🎒 | Urlaubs- & Reise-Packlisten | Kleidung, Elektronik, Dokumente, Kosmetik | -| `inventory` | 📩 | Vorratskammer, Keller, Homelab-Hardware | Regal 1, Gefrierschrank, Server-Rack | -| `todo` | 📝 | Einfache Gruppen-Checklisten | Heute, SpĂ€ter, Ideen | - -### Automatische Kategorisierung & Mengenangaben -- EintrĂ€ge können mit einer Mengenangabe (z.B. `3x`, `500g`, `1 Kasten`) versehen werden. -- Die intelligente Eingabe erkennt Einheiten automatisch oder trennt Mengen ĂŒber Eingabefelder. -- EintrĂ€ge werden ĂŒbersichtlich nach Kategorien gruppiert. Jede Kategorie kann zur besseren Übersicht eingeklappt werden. - ---- - -## ⚡ 2. Echtzeit-Abhaken (Multi-User Live Sync) - -Wenn mehrere Familienmitglieder oder Teammitglieder gleichzeitig im Supermarkt unterwegs sind, synchronisiert MindSift jeden Schritt in Echtzeit ĂŒber WebSockets: - -```mermaid -sequenceDiagram - autonumber - actor Alice as Alice (im Markt) - participant WS as WebSocket Hub - actor Bob as Bob (zu Hause) - - Alice->>WS: PATCH /items/123 (isCompleted: true) - WS->>Bob: Event: ITEM_TOGGLED (itemId: "123", completedBy: "Alice") - Note over Bob: Eintrag wird sofort durchgestrichen
GrĂŒnes Badge "von Alice" erscheint -``` - -- **Transparenz:** Beim Abhaken speichert MindSift `completed_by_name`. Ein dezentes grĂŒnes Pillen-Badge zeigt an, wer den Artikel eingepackt hat. -- **Erledigte EintrĂ€ge:** Abgehakte Artikel wandern nach unten in einen einklappbaren Bereich „Erledigt". -- **Liste leeren:** Ein Klick auf „Erledigte entfernen" löscht alle abgehakten EintrĂ€ge (`LIST_CLEARED`) fĂŒr alle Teilnehmer. - ---- - -## đŸ–ïž 3. Drag & Drop Umsortierung - -Innerhalb einer Liste können EintrĂ€ge frei per Drag & Drop sortiert werden: -1. **Innerhalb einer Kategorie:** Ändert die Reihenfolge (`sort_order`). -2. **Kategorie-Übergreifend:** Zieht der Benutzer einen Eintrag in eine andere Kategorie (z.B. von „KĂŒhlregal" nach „Obst & GemĂŒse"), aktualisiert das Frontend optimistisch die Kategorie und sendet das Update ans Backend. - ---- - -## đŸ‘„ 4. Freigaben & Berechtigungen - -Listen können ĂŒber den einheitlichen Freigabe-Dialog ([`UnifiedShareModal.tsx`](../../frontend/src/components/UnifiedShareModal.tsx)) geteilt werden: -- **Freigabe fĂŒr Teams:** Alle aktuellen und zukĂŒnftigen Mitglieder der gewĂ€hlten Gruppe erhalten automatisch Zugriff. -- **Freigabe fĂŒr Einzelbenutzer:** Gezielter Zugriff fĂŒr einzelne Personen. -- **Rollen:** - - `editor`: Darf EintrĂ€ge hinzufĂŒgen, bearbeiten, abhaken und löschen. - - `viewer`: Nur Lesezugriff (EintrĂ€ge ansehen, aber nicht verĂ€ndern). +- **Kategorien per Hashtag:** `Hafermilch #KĂŒhlregal` landet in der Kategorie „KĂŒhlregal“. Kategorien werden alphabetisch als Abschnitte angezeigt; die Eingabe schlĂ€gt vorhandene Hashtags vor. +- **Menge:** Feld `quantity` (z. B. `2x`, `500g`). +- **Drag & Drop** ordnet EintrĂ€ge um; in eine andere Kategorie gezogen, wird der Hashtag im Text angepasst (`items/reorder` mit `category` und `content`). +- **Abhaken** speichert, wer und wann; Erledigte stehen in einem eigenen Bereich und lassen sich per „Erledigte entfernen“ löschen (`clear-completed`). +- **Freigabe** ĂŒber den Dialog `UnifiedShareModal` (Bearbeiten oder Nur Lesen). Ein Leser sieht Änderungen live, Ă€ndert aber nichts. +- **Live-Sync:** Ereignisse `ITEM_*` und `LIST_*` im Raum der Liste ([websocket.md](../api/websocket.md)). Abhaken erscheint bei allen Beteiligten sofort. +- Aus der Triage lassen sich Gedanken direkt als Eintrag in eine Liste ĂŒbernehmen. diff --git a/Doku/modules/tasks.md b/Doku/modules/tasks.md index 90ec80f..1ad28a5 100644 --- a/Doku/modules/tasks.md +++ b/Doku/modules/tasks.md @@ -1,86 +1,38 @@ -# 📋 Kernmodul 2: Aufgaben & Kanban-System +# Modul: Aufgaben, Kanban & Alarme -Das Aufgaben-Modul ([`TasksView.tsx`](../../frontend/src/features/triage/TasksView.tsx)) bietet eine flexible Aufgabenverwaltung mit frei konfigurierbaren Listen, Kanban-Boards, Subtask-Checklisten und einem automatisierten Multi-Channel Alarm-System. +Code: [TasksView.tsx](../../frontend/src/features/triage/TasksView.tsx), [TaskDetailModal.tsx](../../frontend/src/features/triage/TaskDetailModal.tsx), [ManageColumnsModal.tsx](../../frontend/src/features/triage/ManageColumnsModal.tsx); Backend: `handleUpdateThought`, `handleReorderTasks`, `*TaskList*`-Handler, `startAlarmTicker`. ---- +## Datenmodell -## đŸ—‚ïž 1. Datenmodell & Listen-Hierarchie +Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus`, `priority` (`low`/`normal`/`high`/`urgent`), `dueDate` (nur Datum), `notes` (Markdown), `subtasks` (`[{id, title, isCompleted}]`), `tags`, `taskListId` und `sortOrder`. Aufgaben ohne Liste sind persönlich. -Aufgaben basieren technisch auf der Tabelle **`thoughts`** in SQLite. Ein Eintrag gilt als Aufgabe, wenn `status = 'kept'` oder `(status = 'converted' AND converted_type = 'task')` zutrifft. +## Ansichten -``` -Benutzer (users) - └── Aufgabenlisten (task_lists) - ├── Eigene Listen (z.B. "Privat", "Homelab") - └── Mit Teams geteilte Listen (task_list_shares) - └── Aufgaben (thoughts) - ├── Kanban-Status (todo, in_progress, done) - ├── PrioritĂ€t (low, normal, high, urgent) - ├── FĂ€lligkeitsdatum (dueDate) - ├── Notizen (notes) - └── Subtasks (JSON Array von Teilaufgaben) -``` +- **Liste** mit Filtern (offen, erledigt, Papierkorb), Gruppierung (z. B. nach Status oder FĂ€lligkeit), Sortierung und Hashtag-Filter. +- **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`. +- Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, PrioritĂ€t, FĂ€lligkeit mit Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen). +- Aufgaben lassen sich in eine Notiz umwandeln („Als Notiz vertiefen“). Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknĂŒpft: Abhaken wirkt in beide Richtungen (`SyncDocBlockAndTask`), „In Notiz öffnen“ springt zum Block. -### Listen-Sharing & Avatar-Stack -Aufgabenlisten können fĂŒr Gruppen/Teams freigegeben werden. Ein Avatar-Stack im Listen-Header und in der Tab-Leiste visualisiert alle Mitglieder, die Zugriff auf die jeweilige Liste haben. +## Aufgabenlisten und Freigabe ---- +- Jede Liste hat EigentĂŒmer, Titel, Symbol, Farbe und eigene Kanban-Spalten. +- Freigabe an Gruppen oder Benutzer ĂŒber den Dialog `UnifiedShareModal` (Stufe pro Freigabe): + - **Bearbeiter:** darf Aufgaben anlegen, Ă€ndern, abhaken, verschieben, löschen sowie Liste umbenennen und Spalten Ă€ndern. + - **Leser:** sieht alles, kann nichts Ă€ndern. Der Server antwortet mit `403`; die OberflĂ€che blendet Eingaben aus und zeigt „Nur Lesen“. + - Gruppenmitglieder mit mehreren Wegen erhalten die höchste Stufe. Nur der EigentĂŒmer legt Freigaben an oder entfernt sie und kann die Liste löschen. +- Umsetzung: `Store.GetTaskListPermission` liefert `owner`/`editor`/`viewer`/`""`; `ensureTaskListWritable` schĂŒtzt Schreibpfade. +- Bekannte LĂŒcken: [FLAW-03](../flaws.md) (Umsortieren per ID), [FLAW-06](../flaws.md) (Editor kann weiterteilen). -## đŸ–„ïž 2. Ansichten: Liste vs. Kanban-Board +## Alarme -Der Benutzer kann jederzeit nahtlos zwischen zwei Ansichten umschalten: +Der Ticker lĂ€uft jede Minute ([architecture.md](../architecture.md)). FĂŒr offene Aufgaben mit `due_date <= heute` und `alarm_sent = 0` sendet er an die KanĂ€le des Aufgaben-**EigentĂŒmers**: -### 1. Kompakte Listenansicht -- Optimiert fĂŒr schnelles Abarbeiten und Tastatursteuerung. -- Zeigt Checkbox, Titel, PrioritĂ€ts-Badge, Herkunfts-Pille (`Webhook`, `Diktat`), FĂ€lligkeitsdatum und Subtask-ZĂ€hler (`2/5`). -- 1-Klick-PrioritĂ€ts-Wechsel: Klick auf ein Badge rotiert durch die PrioritĂ€ten. +| Kanal | Ziel | Konfiguration | +|---|---|---| +| E-Mail | `alarmEmail` (sonst Konto-Adresse) | SMTP aus Admin-Einstellungen oder Umgebung (Port 465 mit TLS wird direkt unterstĂŒtzt, sonst STARTTLS ĂŒber `smtp.SendMail`) | +| Discord | Webhook-URL | Benutzereinstellungen | +| Webhook | beliebige URL (JSON: `event`, `title`, `notes`, `dueDate`, `priority`, `timestamp`) | Benutzereinstellungen | -### 2. Kanban-Board (Drag & Drop) -- StandardmĂ€ĂŸig 3 Spalten: **Zu erledigen** (`todo`), **In Arbeit** (`in_progress`) und **Erledigt** (`done`). -- **Freie Spalten-Konfiguration:** Über das Spalten-MenĂŒ können Spalten umbenannt, farblich markiert oder neue Spalten hinzugefĂŒgt werden. -- **Drag & Drop:** Aufgaben können per HTML5 Drag & Drop zwischen Spalten verschoben werden. Das Backend speichert die relative Position (`sort_order`) und den neuen `kanban_status` automatisch. +Danach wird `alarm_sent = 1` gesetzt, sobald mindestens ein Kanal erfolgreich war. ---- - -## 🎯 3. PrioritĂ€ten & Subtasks - -### 4-Stufen-PrioritĂ€tensystem -Aufgaben unterstĂŒtzen vier einheitliche PrioritĂ€tsstufen: -- **Niedrig (`low`):** Dezent grau hinterlegt. -- **Normal (`normal`):** Akzentfarbe. -- **Wichtig (`high`):** Gelb hervorgehoben mit Warndreieck (`⚠ Wichtig`). -- **Dringend (`urgent`):** Rot pulsierend mit Flammen-Icon (`đŸ”„ Dringend`). - -### Subtasks (Teilaufgaben) -Jede Aufgabe kann eine hierarchische Checkliste von Subtasks enthalten: -- Gespeichert als JSON in der Spalte `subtasks`. -- Fortschrittsanzeige im Kartenkopf (z.B. `3/5 erledigt`). -- Einzelnes Abhaken synchronisiert direkt in die Datenbank. - ---- - -## ⏰ 4. Multi-Channel Alarm-Scheduler - -MindSift enthĂ€lt einen serverseitigen Hintergrund-Ticker in Go ([`internal/api/server.go`](../../internal/api/server.go)), der fĂ€llige Aufgaben proaktiv an externe KanĂ€le meldet. - -```mermaid -flowchart TD - Ticker["Go Background Ticker (alle 30s)"] --> Query["SELECT thoughts WHERE due_date <= now + Vorwarnzeit AND alarm_sent = 0"] - Query --> CheckChannels{"Welche KanĂ€le sind beim User aktiv?"} - - CheckChannels -->|email| SendSMTP["E-Mail via SMTP versenden"] - CheckChannels -->|discord| SendDiscord["Discord Webhook POST"] - CheckChannels -->|webhook| SendCustom["Custom HTTP POST Webhook"] - - SendSMTP --> MarkSent["UPDATE thoughts SET alarm_sent = 1"] - SendDiscord --> MarkSent - SendCustom --> MarkSent -``` - -### Alarm-Konfiguration -1. **Vorwarnzeit:** Einstellbar in Minuten (z.B. 15, 30, 60 Minuten vor FĂ€lligkeit). -2. **Kanal-Multi-Select:** Benutzer können beliebig viele KanĂ€le parallel aktivieren: - - **E-Mail:** Versand ĂŒber den in den Admin-Einstellungen hinterlegten SMTP-Server. - - **Discord:** Benachrichtigung mit formatierter Embed-Karte in einen Discord-Kanal. - - **Eigener Webhook:** Beliebige HTTP POST JSON Endpunkte (z.B. Gotify, Ntfy, Home Assistant). -3. **Schutz vor Doppel-Alarmen:** Sobald ein Alarm abgesetzt wurde, wird `alarm_sent = 1` gesetzt. Wird das Datum der Aufgabe spĂ€ter geĂ€ndert, setzt der Server `alarm_sent = 0` automatisch zurĂŒck. +EinschrĂ€nkungen ([FLAW-12](../flaws.md)): FĂ€lligkeiten haben keine Uhrzeit, `notifyBeforeMinutes` wird ignoriert, die Serverzeit gilt, und ein verschobener Termin löst nicht erneut aus. URLs werden nicht geprĂŒft ([FLAW-05](../flaws.md)). diff --git a/Doku/modules/triage.md b/Doku/modules/triage.md index 8e6a6e5..08d8b21 100644 --- a/Doku/modules/triage.md +++ b/Doku/modules/triage.md @@ -1,88 +1,36 @@ -# đŸ“„ Kernmodul 1: Triage & Quick-Dump Engine +# Modul: Schnellerfassung & Triage -Das Triage-Modul von MindSift basiert auf dem *Getting Things Done (GTD)*-Prinzip. Es trennt die **Erfassung von Gedanken** strikt von der **Entscheidung, was damit geschehen soll**. +Ziel: Gedanken in Sekunden festhalten und spĂ€ter in Ruhe sortieren. Code: [InstantCaptureBar.tsx](../../frontend/src/features/capture/InstantCaptureBar.tsx), [TriageDeck.tsx](../../frontend/src/features/triage/TriageDeck.tsx), Backend `handleCaptureInbox`, `handleTriageAction` und `Store.UpdateThoughtTriage`. ---- +## Erfassung -## 💡 1. Das 2-Phasen-Konzept +- Die Eingabeleiste sitzt im Header; `C` oder `/` fokussiert sie (außer beim Tippen in einem Feld). +- Ziel (`target`): **Posteingang** (`inbox`, Standard), **Direkt-Aufgabe** (`task`), **Listeneintrag** (`list_item`, mit Zielliste) oder **Direkt-Notiz** (`document`). +- Mehrzeilige Eingabe wird unterstĂŒtzt. Spracheingabe nutzt die Web Speech API des Browsers; Quelle `voice` fĂŒgt den Tag `#diktat` an. +- Quelle `webhook` (per API-Key/Webhook) fĂŒgt den Tag `#webhook` an. Hashtags im Text werden als Tags extrahiert (`domain.ExtractTags`). +- Per API: `POST /inbox` und `POST /webhooks/ingest` (siehe [rest.md](../api/rest.md)). -1. **Phase 1: Schnelles Erfassen (Instant Dump)** - Ein Gedanke, ein Anruf, eine Idee oder ein Server-Alarm wird ohne Reibung erfasst. Der Benutzer muss in diesem Moment weder ein Projekt auswĂ€hlen, noch FĂ€lligkeitsdaten eingeben oder Ordner durchsuchen. -2. **Phase 2: Fokussierte Triage (Inbox Zero)** - Zu einem spĂ€teren Zeitpunkt (z.B. morgens oder beim Feierabend) sichtet der Benutzer den Stapel im Triage-Deck Karte fĂŒr Karte und trifft fĂŒr jeden Eintrag eine eindeutige Entscheidung. +## Triage-Deck ---- +Der Posteingang (`status = inbox`) wird Karte fĂŒr Karte abgearbeitet. -## 🔄 2. Triage State Machine +| Aktion | Desktop | Mobil | Ergebnis | +|---|---|---|---| +| Verwerfen | `H` oder `←` | Wischen links | `status = discarded` (Papierkorb) | +| Zur Aufgabe | `L` oder `→` | Wischen rechts | `kept`/`converted` als Aufgabe | +| Aufgabe mit Termin | `T` | AktionsmenĂŒ | wie oben, mit FĂ€lligkeit | +| In Notiz wandeln | `D` | AktionsmenĂŒ | neues Dokument, Gedanke wird `converted` (`document`) | +| SpĂ€ter | Leertaste | AktionsmenĂŒ | Karte ans Stapelende | +| MenĂŒ schließen | `Esc` | – | – | -```mermaid -stateDiagram-v2 - [*] --> Capture: Gedanke erfasst (Desktop / Mobile / Webhook) - - Capture --> Inbox: Standard (status: 'inbox') - Capture --> Task: Direkt als Aufgabe (target: 'task') - Capture --> Document: Direkt als Notiz (target: 'document') - Capture --> ListItem: Direkt auf Liste (target: 'list_item') +Die Umwandlung in eine Notiz ĂŒbernimmt Titel, Erledigt-Status, Notizen, Unteraufgaben sowie FĂ€lligkeit und PrioritĂ€t als eingerĂŒckte Blöcke. Eine Umwandlung in einen Listeneintrag legt einen Eintrag in der gewĂ€hlten Liste an und benachrichtigt deren Teilnehmer. - state Inbox { - [*] --> ActiveCard: Oben auf Stapel - ActiveCard --> Skipped: Leertaste / SpĂ€ter (Ans Ende) - Skipped --> ActiveCard - } +## ZustĂ€nde eines Gedankens - ActiveCard --> Discarded: Swipe links / H (Wegwerfen) - ActiveCard --> Task: Swipe rechts / L (Als Aufgabe) - ActiveCard --> TaskDated: T (Mit FĂ€lligkeitsdatum) - ActiveCard --> Document: D (In Notiz umwandeln) - ActiveCard --> ListItem: Auf Liste schieben - - Discarded --> Trash: status: 'discarded' - Task --> TasksModule: status: 'kept', converted: 'task' - TaskDated --> TasksModule: status: 'kept', converted: 'task', dueDate - Document --> WorkspaceModule: status: 'converted', converted: 'document' - ListItem --> ListsModule: status: 'converted', converted: 'list_item' +``` +inbox ──keep──▶ kept ──convert──▶ converted (task | document | list_item) + │ │ + └─discard─▶ discarded ──restore──▶ inbox (Papierkorb leeren: DELETE /thoughts/discarded/purge) ``` ---- - -## đŸ–ïž 3. Steuerung & Gesten (Mobile vs. Desktop) - -### Desktop-Bedienung (Tastatur-First) -- `C` oder `/`: Fokussiert sofort die Eingabeleiste oben im Header. -- `Enter`: Speichert den Gedanken sofort im Deck. -- `←` oder `H`: Aktive Karte wegwerfen (`discard`). -- `→` oder `L`: Aktive Karte sofort zur Aufgabe machen (`task`). -- `T`: Datumsauswahl fĂŒr Termin-Aufgabe öffnen. -- `D`: Aktive Karte in eine vollwertige Workspace-Notiz umwandeln. -- `Leertaste`: Karte ĂŒberspringen und ans Ende des Stapels schieben („SpĂ€ter"). - -### Mobile Bedienung (Touch & Swipe) -- **Mittlerer Plus-Button (`+`):** Öffnet das design-angeglichene Bottom-Sheet mit Spracheingabe, 4 PrioritĂ€ts-Buttons (`Niedrig`, `Normal`, `Wichtig`, `Dringend`) und FĂ€lligkeitsdatum. -- **Wischgeste nach links (Swipe Left):** Roter Indikator erscheint, Karte wird verworfen. -- **Wischgeste nach rechts (Swipe Right):** GrĂŒner Indikator erscheint, Karte wird sofort zur Aufgabe. -- **Long-Press (Langes DrĂŒcken auf Karte):** Öffnet das Schnell-Aktions-MenĂŒ (Auf Liste, Mit Termin, Als Notiz, SpĂ€ter). - ---- - -## đŸŽ™ïž 4. Spracheingabe (Speech-to-Text) - -In der Desktop-Schnelleingabeleiste ([`InstantCaptureBar.tsx`](../../frontend/src/features/capture/InstantCaptureBar.tsx)) sowie im mobilen Triage-Modal ([`App.tsx`](../../frontend/src/App.tsx)) ist ein nativer Browser-Sprachassistent integriert: -- Verwendet die standardisierte `SpeechRecognition` bzw. `webkitSpeechRecognition` API. -- Erkennt Sprache auf Deutsch (`lang: 'de-DE'`). -- Live-Transkription: Gesprochener Text wird in Echtzeit in das Textfeld eingefĂŒgt. -- Haptisches Feedback: Vibrieren beim Starten und Stoppen der Aufnahme. -- Markiert erfasste Gedanken automatisch mit dem Herkunfts-Attribut `source: 'voice'` (Diktat-Badge). - ---- - -## ⚡ 5. Webhook-Ingestion & Smarthome-Pille - -MindSift kann externe Alarme (Home Assistant, Zigbee2MQTT, n8n, Proxmox, Monitoring) automatisch ins Triage-Deck einspeisen: - -### Herkunftserkennung -1. Wird ein Gedanke ĂŒber `/api/v1/inbox` mit `X-API-Key` oder ĂŒber `/api/v1/webhooks/ingest` eingeliefert, setzt das Backend automatisch `source = "webhook"`. -2. Im Frontend visualisiert die Triage-Karte eine dezente Webhook-Pille (` Webhook`). -3. Diese Kennzeichnung bleibt auch erhalten, wenn die Karte spĂ€ter zu einer Aufgabe umgewandelt wird. - -### Live-WebSocket Update -Nach erfolgreichem Ingest sendet der Server sofort ein `TASK_CREATED` Event ĂŒber den WebSocket-Hub. Das geöffnete Triage-Deck fĂŒgt die neue Alarm-Karte sofort oben in den Stapel ein – ohne dass der Benutzer die Seite neu laden muss. +Ein Gedanke mit `status = kept` zĂ€hlt bereits als Aufgabe und erscheint in der Aufgabenansicht. diff --git a/Doku/modules/workspace.md b/Doku/modules/workspace.md index a0d93f7..857c1ee 100644 --- a/Doku/modules/workspace.md +++ b/Doku/modules/workspace.md @@ -1,104 +1,39 @@ -# 📝 Kernmodul 3: Workspace Outliner-Engine, Wiki-Links & Pure-DB Storage +# Modul: Workspace (Outliner-Notizen) -Der MindSift Workspace ([`WorkspaceView.tsx`](../../frontend/src/features/workspace/WorkspaceView.tsx)) kombiniert ein **hierarchisches Bullet-Point Notizsystem (inspiriert von Logseq)** mit einem **persönlichen Wiki**, **Live-Kollaboration** und einem **globalen To-Do Rollup Dashboard**. +Code: [WorkspaceView.tsx](../../frontend/src/features/workspace/WorkspaceView.tsx) (Explorer, Tags, To-Do-Rollup), [DocumentEditor.tsx](../../frontend/src/features/workspace/DocumentEditor.tsx), [OutlinerTree.tsx](../../frontend/src/features/workspace/OutlinerTree.tsx), [OutlinerBlock.tsx](../../frontend/src/features/workspace/OutlinerBlock.tsx), [CodeBlockItem.tsx](../../frontend/src/features/workspace/CodeBlockItem.tsx), [LinkModal.tsx](../../frontend/src/features/workspace/LinkModal.tsx); Backend: Dokument-, Ordner- und Tag-Handler, `Store.PatchDocumentBlocks`. ---- +## Datenhaltung -## 🌳 1. Die Logseq-Outliner-Engine +- Eine Notiz ist ein Datensatz in `documents` plus geordnete Zeilen in `blocks` (`position` als Kommazahl, `level` fĂŒr die EinrĂŒckung). Alles liegt in SQLite; es gibt keine Dateien auf der Platte. +- Bei jeder Änderung wird `documents.markdown_cache` neu erzeugt; `block_tags` wird aus `#Hashtags` der Blöcke neu aufgebaut. +- Der Export als `.md` erfolgt im Browser (Button im Editor, Blob-Download). +- Blocktypen: Überschriften `h1`–`h3`, `paragraph`/`text`, `bullet`, `todo`, `code`, `callout`, `divider`, `table`, `progress`. -Im Gegensatz zu klassischen Fließtext-Editoren basiert der MindSift Workspace auf einer **flachen, relationalen Liste von Blöcken** (`blocks`-Tabelle in SQLite). Jeder Block ist ein gedanklicher Knotenpunkt mit einer flexiblen EinrĂŒckungsebene (`level: 0, 1, 2, ...`) und einer fortlaufenden Fließkommaposition (`position`): +## Editor -``` -‱ Projekt MindSift v2.0 (level 0) - ‱ Architektur-Entscheidung (level 1) - - [x] Database-First Blocks implementieren (level 2) - - [ ] Performance Benchmark mit 10.000 Blöcken (level 2) - ‱ Notizen aus dem Dev-Meeting (level 1) - > Wichtig: Keine verschachtelten SQLite Queries ausfĂŒhren! (level 2) -``` +- **Outliner:** `Enter` neuer Block, `Tab`/`Shift+Tab` ein-/ausrĂŒcken, `Shift+Enter` weicher Umbruch, Blöcke ein-/ausklappen. +- **Zoom (Hoisting):** Klick auf den Bullet fokussiert den Ast; die Breadcrumb-Leiste fĂŒhrt zurĂŒck. +- **Drag & Drop** verschiebt Blöcke samt UnterĂ€sten; auf einen anderen Block gezogen, wird der Block zum Unterpunkt. +- **Slash-Befehle:** `/todo`, `/link`, `/h1`, `/h2`, `/h3`, `/code`, `/callout`, `/divider` (Liste in `OutlinerBlock.tsx`). +- **Links:** `[[Notiztitel]]`, Markdown-Links `[Titel](url)` und reine URLs werden als Token dargestellt; `LinkModal` hilft beim VerknĂŒpfen von Notizen. +- **EinfĂŒgen:** Mehrzeiliger Text wird anhand von Markdown-PrĂ€fixen und EinrĂŒckung in Blöcke zerlegt; Überschriften gruppieren darunterliegenden Text und Code. +- **Speichern:** Der Editor speichert 800 ms nach der letzten Eingabe das gesamte Dokument (`PUT /documents/{id}`). Der schlanke Block-Endpunkt `PATCH /documents/{id}/blocks` wird derzeit vom To-Do-Rollup beim Abhaken genutzt. -### Tastatur-Navigation & Shortcuts -- `Enter`: Erstellt einen neuen Block auf derselben Hierarchieebene. -- `Tab`: RĂŒckt den aktuellen Block ein (wird Kindknoten des vorherigen Blocks). -- `Shift + Tab`: RĂŒckt den Block um eine Ebene aus (verringert die Tiefe). -- `Shift + Enter`: Weicher Zeilenumbruch innerhalb desselben Blocks (z.B. fĂŒr mehrzeilige Notizen oder Code). -- `Backspace` (auf leerem Block): Löscht den Block sofort. Das AusrĂŒcken erfolgt strikt und kontrolliert ĂŒber `Shift + Tab`. -- `/` (Slash-Commands): Öffnet das UmwandlungsmenĂŒ (`/todo`, `/link`, `/h1`, `/h2`, `/h3`, `/code`, `/callout`, `/quote`, `/divider`). -- **Markdown-Tastatur-KĂŒrzel:** Tippen von `[] ` oder `[ ] ` wandelt den Block sofort in ein Todo um; `# `, `## `, `### ` erzeugt Überschriften. +## Explorer, Tags, To-Dos -### EingabestabilitĂ€t & Zombie-Fokus-Schutz (`OutlinerBlock.tsx`) -1. **Echter Fokus-Check:** Remote-Updates (`block_updated`) werden nur dann abgewehrt, wenn der Block tatsĂ€chlich aktiv im Betriebssystem editiert wird: - `isFocusedRef.current === true && document.hasFocus()`. -2. **Sofort-Flush bei Fensterwechsel:** Sobald das Browser-Fenster den Fokus verliert (`window.addEventListener('blur', ...)`), wird ein offener 150ms-Debounce sofort gecancelt und der Text augenblicklich per WebSocket ĂŒbertragen. -3. **Stale Closures eliminiert:** Alle Callbacks werden in `useRef` gehalten, sodass asynchrone Debounce-Timer garantiert die aktuellste Komponenten-Referenz verwenden. +- Ordnerbaum mit Favoriten (`doc_pins`), Verschieben per Drag & Drop (`/documents/{id}/move`, `/folders/move`). +- **Tags:** Alle `#Hashtags` werden aggregiert (`/workspace/tags`). Eine Tag-Seite zeigt alle Fundstellen gruppiert nach Notiz und kann eine eigene Freitext-Notiz tragen (`tag_pages`). +- **To-Do-Rollup:** Die Ansicht „To-Dos“ sammelt alle `todo`-Blöcke aller Notizen, filterbar nach Ordner. Abhaken im Rollup Ă€ndert den Block in der Notiz. +- **Enhanced Tasks:** Ein `todo`-Block lĂ€sst sich als verknĂŒpfte Aufgabe fĂŒhren (`/documents/{id}/blocks/{blockId}/enhanced-task`); Erledigt-Status bleibt zwischen Notiz und Aufgabenansicht synchron. -### Zoom-In Hoisting (Fokus-Modus) -Klickt der Benutzer auf den Bullet-Dot (`‱`) eines Blocks, blendet der Editor alle ĂŒbergeordneten und benachbarten Blöcke aus und zoomt **ausschließlich in diesen Ast** hinein: -- Der gewĂ€hlte Punkt wird zur prominenten Überschrift im Fokus-Modus. -- Eine klickbare Breadcrumb-Leiste oben zeigt den Pfad an (z.B. `Workspace / Projekte / MindSift / Architektur`). -- Ein Klick auf ein beliebiges Breadcrumb-Element zoomt sofort auf die jeweilige Ebene zurĂŒck. +## Freigabe -### Drag & Drop von Blöcken -Der Bullet-Dot dient gleichzeitig als Drag-Anfasser. Wird ein Block verschoben, erkennt die Engine automatisch alle untergeordneten Kind-Blöcke (`isDescendantBlock`) und verschiebt den **kompletten Unterbaum** an die neue Zielposition. +- Einzelne Notizen und ganze **geteilte Ordner** lassen sich an Benutzer oder Gruppen geben. EmpfĂ€nger haben **nur Leserechte**; nur der EigentĂŒmer schreibt ([sharing-and-permissions.md](../sharing-and-permissions.md), [FLAW-17](../flaws.md)). +- EmpfĂ€nger ordnen geteilte Notizen und Ordner in ihrem eigenen Baum ein (`doc_user_folders`, `workspace_folder_mounts`), ohne dass der EigentĂŒmer etwas davon merkt. +- Änderungen erreichen offene Fenster ĂŒber `DOC_BLOCKS_PATCHED`/`DOC_UPDATED` ([websocket.md](../api/websocket.md)). ---- +## Grenzen -## 🔗 2. Wiki-Links (`[[Link]]`), Web-Links (`/link`) & Hashtags - -1. **Interne Wiki-Links:** - - Text in doppelten eckigen Klammern (z.B. `[[Reiseplanung 2026]]`) wird automatisch als klickbare Notiz-Pille gerendert. - - Klick darauf öffnet die verknĂŒpfte Notiz direkt im Editor. -2. **Slash-Befehl `/link` & Link-Modal:** - - `/link` öffnet ein Modal mit zwei Modi: - - **Notiz verknĂŒpfen:** Durchsucht bestehende Notizen und fĂŒgt `[[Titel]]` ein. - - **Web-Link:** Eingabe von Titel und URL, fĂŒgt Standard-Markdown `[Titel](url)` ein. -3. **Hashtags (`#tag`):** - - Hashtags im Fließtext werden automatisch indiziert (`block_tags`-Tabelle). - - In der linken Leiste listet der Tag-Explorer alle verwendeten Schlagworte auf. Klick auf ein Tag filtert alle Dokumente und Blöcke, die dieses Tag enthalten. - ---- - -## ⚡ 3. Echtzeit-Kollaboration & Automatische Markdown-Kompilierung - -MindSift setzt auf eine latenzfreie In-Memory Kollaborations-Architektur ([`internal/ws/docroom.go`](../../internal/ws/docroom.go)): - -### Funktionsweise -1. **In-Memory DocRoom pro geöffnetem Dokument:** - - HĂ€lt den synchronen Zustand von `title` und `blocks[]` im RAM. - - Granulare Mutations-Events (`block_updated`, `block_created`, `block_deleted`, `block_moved`, `title_updated`). - - Ephemeres Presence-Tracking (`cursor_moved`, `client_left`) zeigt farbige Benutzer-Avatare im Header und Cursors am aktiven Block an. -2. **Echo-Schutz:** - - Jeder Client identifiziert sich mit einer eindeutigen `clientId`. - - Der Server sendet Mutations-Events ausschließlich an andere Clients im selben Raum (`clientId != senderClientId`), um Cursor-Springen und Selbstechos zu verhindern. - -### Automatische Markdown-Kompilierung & SQLite Debounce-Flush -``` -Eingabe im OutlinerBlock - │ - ├──▶ WebSocket (block_updated Event) - │ │ - │ â–Œ - │ Go DocRoom: In-Memory Mutation & Broadcast an Peers - │ - └──▶ 1.5s InaktivitĂ€ts-Debounce (oder Raum-Exit) - │ - â–Œ - SQLite Transaktion: - 1. Persistiert Blöcke in `blocks` - 2. Regex-Extraktion von #tags in `block_tags` - 3. Kompiliert Block-Hierarchie zu Standard-Markdown - und speichert Cache in `documents.markdown_cache` -``` - -1. **Kompiliertes Markdown:** Jeder Block wird gemĂ€ĂŸ seiner `level`-Tiefe mit 2 Leerzeichen pro EinrĂŒckungsebene als sauberes Markdown formatiert (`documents.markdown_cache`). -2. **Pure Database Architecture:** Alle Daten liegen transaktional und sicher in SQLite. Es werden keine unkontrollierten Dateien auf der Festplatte abgelegt; Notizen können jederzeit per Klick als `.md`-Datei exportiert werden. - ---- - -## 📊 4. Globaler To-Do Rollup Dashboard - -Über den Tab **„Globale To-Dos"** bietet der Workspace eine aggregierte Übersicht aller im gesamten Wiki verteilten Aufgaben: -- Scannt in Echtzeit alle unvollendeten `- [ ] ` Checkboxen ĂŒber alle Ordner und Notizen hinweg. -- Übersichtliche Gruppierung nach **Ordner ➔ Dokument ➔ Aufgabe**. -- **Ordner-Filter:** Klick auf einen Ordner isoliert nur dessen Aufgaben fĂŒr fokussiertes Arbeiten. -- **2-Wege-Live-Sync:** Wird eine Checkbox im Rollup abgehakt, wird der Block in SQLite sofort auf `checked = 1` aktualisiert und via WebSocket an alle aktiven Clients gebroadcastet. +- Keine gleichzeitige Mehrbenutzerbearbeitung, keine Cursor-Anzeige, keine Versionshistorie. +- Keine Virtualisierung langer Dokumente; sehr große Notizen können auf MobilgerĂ€ten trĂ€ge werden *(nicht gemessen)*. +- Suche (`/search`) erfasst nur Titel und Inhalt eigener Notizen ([FLAW-18](../flaws.md)). diff --git a/Doku/reverse-proxy.md b/Doku/reverse-proxy.md index 7a3615e..ebf6a6d 100644 --- a/Doku/reverse-proxy.md +++ b/Doku/reverse-proxy.md @@ -8,9 +8,9 @@ Dieses Dokument beschreibt die empfohlene Konfiguration von Reverse-Proxies und | Protokoll | Standard-Port | Benötigte Header & Einstellungen | Zweck | | :--- | :--- | :--- | :--- | -| **WebSockets** | 443 (WSS) / 80 (WS) | `Upgrade $http_upgrade`
`Connection "Upgrade"`
`proxy_read_timeout 3600s` | Bidirektionale Yjs-CRDT-Synchronisation & Awareness | +| **WebSockets** | 443 (WSS) / 80 (WS) | `Upgrade $http_upgrade`
`Connection "Upgrade"`
`proxy_read_timeout 3600s` | Bidirektionale Live-Updates (Aufgaben, Listen, Notizen) | | **Server-Sent Events (SSE)** | 443 (HTTPS) / 80 (HTTP) | `X-Accel-Buffering: no`
`proxy_buffering off`
`proxy_cache off` | Fallback bei blockiertem WebSocket-Upgrade | -| **REST API & Uploads** | 443 (HTTPS) / 80 (HTTP) | `client_max_body_size 64M` | Notizen, AnhĂ€nge & CRDT-Zustandsblöcke | +| **REST API & Uploads** | 443 (HTTPS) / 80 (HTTP) | `client_max_body_size 64M` | Notizen, Import/Export (JSON) | --- @@ -38,7 +38,7 @@ server { ssl_certificate /etc/letsencrypt/live/mindsift.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mindsift.example.com/privkey.pem; - # Maximale Payload-GrĂ¶ĂŸe fĂŒr Backups und große CRDT-ZustĂ€nde + # Maximale Payload-GrĂ¶ĂŸe fĂŒr große Notizen und Exporte client_max_body_size 64M; # MindSift Frontend & REST API @@ -132,7 +132,7 @@ Caddy unterstĂŒtzt WebSockets und HTTP/2 Server-Sent Events von Haus aus vollst mindsift.example.com { encode gzip zstd - # Notizen, CRDT & Event-Stream ohne Pufferung weiterleiten + # Event-Stream (SSE) ohne Pufferung weiterleiten reverse_proxy 127.0.0.1:8080 { # Streaming fĂŒr SSE optimieren flush_interval -1 @@ -148,7 +148,7 @@ Wird MindSift hinter dem Cloudflare CDN betrieben: 1. **WebSockets aktivieren:** - Navigiere in das Cloudflare Dashboard: **Network** > **WebSockets** > Schalter auf **ON** stellen. 2. **Cloudflare Rocket Loader / Auto Minify:** - - Deaktiviere "Rocket Loader" fĂŒr MindSift, da asynchrone Skripteingriffe die Yjs-CRDT-Initialisierung stören können. + - Deaktiviere "Rocket Loader" fĂŒr MindSift, da asynchrone Skripteingriffe den Start der Single-Page-App stören können. 3. **Cloudflare WAF / Bot Fight Mode:** - Stelle sicher, dass `GET /api/v1/events` und `GET /api/v1/ws*` nicht von "Challenge"-Seiten blockiert werden. 4. **SSE-UnterstĂŒtzung:** diff --git a/Doku/sharing-and-permissions.md b/Doku/sharing-and-permissions.md index da90f35..426c175 100644 --- a/Doku/sharing-and-permissions.md +++ b/Doku/sharing-and-permissions.md @@ -1,96 +1,55 @@ -# đŸ‘„ Synchrones Teilen & Berechtigungskonzept +# Teilen & Berechtigungen -MindSift bietet ein mehrstufiges Kollaborations- und Freigabesystem. Es gewĂ€hrleistet strikte DatensouverĂ€nitĂ€t und PrivatsphĂ€re, ermöglicht aber gleichzeitig reibungslose Zusammenarbeit in Teams, Familien oder Unternehmen. +## Rollen ---- +| Ebene | Werte | Bedeutung | +|---|---|---| +| Server | `admin`, `member` | Admins verwalten Benutzer, Einladungen und Einstellungen (`/admin/*`). | +| Gruppe | `admin`, `member` | Gruppen-Admins Ă€ndern/löschen die Gruppe und verwalten Mitglieder. Wer eine Gruppe anlegt, wird Admin. | +| Ressource | siehe unten | EigentĂŒmer, Bearbeiter, Leser. | -## đŸ›Ąïž 1. Globale Rollen & System-Isolation +## Berechtigungen je Ressource -### Globale Rollen -- **Server-Admin (`role = 'admin'`):** Vollzugriff auf System-Einstellungen, E-Mail-Konfiguration (SMTP), Benutzerverwaltung, Einladungscodes und System-Statistiken. -- **Mitglied (`role = 'member'`):** RegulĂ€rer Benutzer; arbeitet in seinen eigenen Notizen, Aufgaben und freigegebenen Teams. +| Ressource | EigentĂŒmer | Bearbeiter | Leser | Freigabe an | +|---|---|---|---|---| +| **Aufgabenliste** (`task_list_shares.permission`) | alles, löschen, (un)teilen | Aufgaben anlegen/Ă€ndern/abhaken/verschieben/löschen, Liste umbenennen, Spalten Ă€ndern | nur ansehen | Benutzer, Gruppen | +| **Geteilte Liste** (`list_shares.role`) | alles, löschen, (un)teilen | EintrĂ€ge anlegen/Ă€ndern/abhaken/löschen | nur ansehen | Benutzer, Gruppen | +| **Notiz** (`doc_shares`, `doc_team_shares`) | schreiben | – | nur lesen | Benutzer, Gruppen | +| **Geteilter Ordner** (`workspace_folder_shares`) | schreiben | – | nur lesen | Benutzer, Gruppen | -### System-Isolations-Modi (`sharing_mode`) -Der Server-Admin legt global fest, wie Benutzer miteinander interagieren dĂŒrfen: +- **Notizen und Ordner sind absichtlich schreibgeschĂŒtzt fĂŒr EmpfĂ€nger.** Das erzwingt eine Migration beim Start (`permission = 'read'`); Schreibpfade im Code sind tot ([FLAW-17](flaws.md)). +- Bei Aufgabenlisten und Listen gilt: Hat jemand mehrere Zugriffswege (direkt und ĂŒber Gruppen), zĂ€hlt die **höchste** Stufe. +- FĂŒr Gruppen gilt die Freigabe fĂŒr alle aktuellen und kĂŒnftigen Mitglieder. +- Aufgabenlisten besitzen zusĂ€tzlich das Altfeld `team_id`: Mitglieder dieser Gruppe haben ohne Freigabezeile Bearbeiterrechte. Eine Gruppenfreigabe ĂŒber den Dialog setzt `team_id`, falls es noch leer ist. -``` -┌────────────────────────────────────────────────────────────────────────┐ -│ System-Isolations-Modi │ -â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”Źâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”Źâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ -│ strict_private │ teams_only │ open │ -│ (Strikt Isoliert) │ (Teams & Gruppen) │ (Offene Instanz) │ -│ │ [EMPFOHLENER STANDARD]│ │ -├──────────────────────┌─────────────────────────┌──────────────────────── -│ Kein Adressbuch │ Adressbuch zeigt nur │ Alle Benutzer sehen │ -│ Keine Freigaben │ gemeinsame Teammit- │ alle anderen Konten │ -│ Autarke Einzel-Instanz│ glieder │ im System │ -└──────────────────────┮─────────────────────────┮───────────────────────┘ -``` +## Aufgabenlisten teilen (Details) -1. **`strict_private`:** VollstĂ€ndige Abschottung. Benutzer wissen nicht voneinander. Keine Freigaben möglich. -2. **`teams_only` (Standard):** Kollaboration ist strikt an Gruppen gebunden. Ein Benutzer kann Objekte nur mit Personen teilen, die mindestens in einer gemeinsamen Gruppe/Team mit ihm sind. -3. **`open`:** Geeignet fĂŒr Familien oder kleine Firmen. Jeder Benutzer kann fĂŒr jeden anderen Benutzer Objekte freigeben. +1. EigentĂŒmer öffnet „Teilen“ an der Liste, wĂ€hlt Gruppe oder Benutzer und die Stufe **Bearbeiten** oder **Nur Lesen**. +2. `POST /task-lists/{id}/shares` speichert `permission`; erneutes HinzufĂŒgen desselben Ziels Ă€ndert die Stufe. +3. `GET /task-lists` liefert je Liste `permission` (`owner`/`editor`/`viewer`). Das Frontend blendet fĂŒr `viewer` alle Eingabemöglichkeiten aus und zeigt „Nur Lesen“. +4. Der Server prĂŒft unabhĂ€ngig davon jeden Schreibzugriff (`403`, `storage.ErrTaskListReadOnly`). ---- +Bestehende Freigaben aus der Zeit vor dieser Stufe wurden mit `editor` migriert, behalten also ihr Verhalten. -## 🏱 2. Gruppen & Teams (`teams` & `team_members`) +## Isolations-Modi (`system_settings.sharing_mode`) -Gruppen strukturieren Benutzer (z.B. „Familie", „Entwicklung", „Marketing"): -- **Gruppen-Admin:** Darf Mitglieder einladen, Rollen Ă€ndern oder die Gruppe löschen. -- **Gruppen-Mitglied:** Hat Zugriff auf alle mit der Gruppe geteilten Ressourcen (Ordner, Aufgabenlisten, Einkaufslisten, Notizen). +Werte: `strict_private`, `teams_only` (Standard), `open`. Gedacht ist: -Wird eine Ressource fĂŒr eine Gruppe freigegeben, erhalten **alle aktuellen und zukĂŒnftigen Mitglieder** der Gruppe automatisch Zugriff, ohne dass Einzelberechtigungen gepflegt werden mĂŒssen. +| Modus | Zweck | +|---|---| +| `strict_private` | Keine Sichtbarkeit zwischen Benutzern | +| `teams_only` | Freigaben nur zwischen Mitgliedern gemeinsamer Gruppen | +| `open` | Jeder kann fĂŒr jeden freigeben | ---- +**TatsĂ€chlich** wird der Modus nur in `GetDiscoverableUsers` ausgewertet, und dort liefert `teams_only` bereits alle Benutzer. `GET /collaborators` ignoriert den Modus, und keine Freigabe-Funktion prĂŒft ihn. Die Einstellung hat derzeit also kaum Wirkung ([FLAW-10](flaws.md)). -## 📁 3. Entkoppelte Ordner-Freigabe (Decoupled Folder Mounts) +## Entkoppelte Ablage geteilter Notizen und Ordner -### Das traditionelle Problem -In herkömmlichen Cloud-Systemen (wie Google Drive oder Nextcloud) fĂŒhrt das Verschieben eines geteilten Ordners oft dazu, dass er auch bei allen anderen Teilnehmern den Platz wechselt. Verschiebt ein Kollege den Ordner in sein Unterverzeichnis „Archiv", verschwindet er beim Ersteller aus dem Sichtfeld. +- **Geteilte Ordner:** Der Ordner existiert einmal (`workspace_shared_folders`); jeder EmpfĂ€nger hat seinen eigenen EinhĂ€ngepfad (`workspace_folder_mounts.mount_path`). Verschiebt ein EmpfĂ€nger den Ordner, Ă€ndert sich nur sein Pfad. Der Unterpfad innerhalb des Ordners (`documents.sub_folder_path`) gilt fĂŒr alle. +- **Einzelne Notizen:** Jeder EmpfĂ€nger kann eine geteilte Notiz in einen eigenen Ordner legen (`doc_user_folders`); die Notiz des EigentĂŒmers bleibt unberĂŒhrt. -### Die MindSift-Lösung: UnabhĂ€ngige EinhĂ€ngepunkte (Mounts) -MindSift entkoppelt die physische Ordner-IdentitĂ€t von der persönlichen Ordner-Hierarchie des Benutzers: +## Sitzungen und Zugriff -``` -EigentĂŒmer (Alice): - Ordner: "Kundenprojekte" - └── Geteilter Ordner: "Projekt Phoenix" (wsf_123) - ├── spec.md - └── roadmap.md - -EmpfĂ€nger (Bob): - Ordner: "Meine Aufgaben" - └── "Wichtiges" - └── EinhĂ€ngepunkt: "Projekt Phoenix" (wsf_123 eingehĂ€ngt via mount_path) - ├── spec.md - └── roadmap.md -``` - -#### Wie es technisch funktioniert: -1. Der geteilte Ordner existiert als EntitĂ€t in `workspace_shared_folders`. -2. Beim EigentĂŒmer liegt der Ordner unter seinem Pfad. -3. Jeder EmpfĂ€nger erhĂ€lt in `workspace_folder_mounts` seinen **eigenen lokalen `mount_path`**. -4. **UnabhĂ€ngiges Verschieben:** Schiebt Bob den Ordner an eine andere Stelle in seinem Dateibaum, wird **ausschließlich sein persönlicher `mount_path`** aktualisiert. Alice und andere Teammitglieder bemerken davon nichts. -5. **Inhaltliche Synchronisation:** Verschiebt, erstellt oder bearbeitet jemand eine Notiz *innerhalb* des geteilten Ordners, wird der relative Unterpfad (`sub_folder_path`) synchronisiert, sodass alle dieselbe interne Dokumentstruktur sehen. - ---- - -## 📄 4. Entkoppelte Notizen-Freigabe (`doc_user_folders`) - -Dasselbe Prinzip gilt fĂŒr einzeln geteilte Dokumente: -- ErhĂ€lt Benutzer Bob eine einzelne Notiz von Alice freigegeben, kann Bob diese Notiz in jeden beliebigen seiner eigenen Ordner einsortieren. -- Seine persönliche Zuordnung wird in der Tabelle `doc_user_folders` (`user_id`, `doc_id`, `folder_path`) gespeichert. -- Die Original-Notiz von Alice verbleibt an ihrem ursprĂŒnglichen Platz. - ---- - -## 📋 5. Geteilte Aufgaben- & Einkaufslisten - -- **Aufgabenlisten (`task_list_shares`):** Freigabe fĂŒr Teams oder Einzelbenutzer, Spalte `permission`: - - `editor`: Aufgaben anlegen, bearbeiten, abhaken, verschieben und löschen; Liste umbenennen und Spalten anpassen. - - `viewer`: Nur Lesezugriff. Der Server lehnt Schreibzugriffe mit `403` ab (`storage.ErrTaskListReadOnly`). - - Hat ein Benutzer ĂŒber mehrere Wege Zugriff (direkt und ĂŒber Gruppen), gilt die höchste Berechtigung. -- **Geteilte Listen (`list_shares`):** - - Rolle `editor`: EintrĂ€ge hinzufĂŒgen, Ă€ndern, abhaken und löschen. - - Rolle `viewer`: Nur Lesezugriff (Echtzeit-Ansicht). -- **Einheitlicher Freigabe-Dialog:** [`UnifiedShareModal.tsx`](../../frontend/src/components/UnifiedShareModal.tsx) bĂŒndelt alle Freigabeoptionen (Teams, Benutzer, Berechtigungsstufen) in einer konsistenten BenutzeroberflĂ€che. +- Tokens werden bei PasswortĂ€nderung, Rollenwechsel, Deaktivierung und „Alle Sitzungen beenden“ ungĂŒltig (`token_version`); ausgeloggte Tokens landen in `revoked_tokens`. +- Registrierung nur per Einladungscode oder, wenn vom Admin freigeschaltet, offen. Das erste Konto wird immer Administrator. +- Bekannte LĂŒcken in der Zugriffskontrolle: [FLAW-01](flaws.md), [FLAW-02](flaws.md), [FLAW-03](flaws.md), [FLAW-06](flaws.md), [FLAW-07](flaws.md), [FLAW-08](flaws.md), [FLAW-24](flaws.md). diff --git a/Doku/theming.md b/Doku/theming.md index 9eb3bdd..bcf387e 100644 --- a/Doku/theming.md +++ b/Doku/theming.md @@ -6,7 +6,7 @@ MindSift implementiert ein radikal einfaches, modulares Theming-System. Anstatt ## 💡 1. Philosophie: 1 Datei = 1 komplettes Theme -Jedes Theme liegt im Verzeichnis [`frontend/src/themes/`](../../frontend/src/themes/) und definiert alle visuellen Eigenschaften ĂŒber standardisierte CSS-Variablen. +Jedes Theme liegt im Verzeichnis [`frontend/src/themes/`](../frontend/src/themes/) und definiert alle visuellen Eigenschaften ĂŒber standardisierte CSS-Variablen. ### Trennung von Struktur und Design - **React-Komponenten** definieren ausschließlich semantische Klassen (wie `.panel-surface`, `.input-field`, `.btn-primary`, `text-main`, `bg-canvas`). @@ -78,17 +78,17 @@ Jede Theme-Datei gliedert sich in sechs exakt definierte Bereiche: | Theme-ID | Datei | Stil & Charakter | Standard-Geometrie | | :--- | :--- | :--- | :--- | -| `dark` | [`theme-dark.css`](../../frontend/src/themes/theme-dark.css) | Standard Dark Slate / Monochrom | Streng eckig (`0px` Cards, `2px` Buttons) | -| `light` | [`theme-light.css`](../../frontend/src/themes/theme-light.css) | Daylight Clean White / Minimalistisch | Modern dezent (`4px` Cards, `4px` Buttons) | -| `high-contrast` | [`theme-high-contrast.css`](../../frontend/src/themes/theme-high-contrast.css) | Reines Schwarz/Weiß mit AAA-Kontrast | Streng eckig (`0px`, krĂ€ftige Rahmen) | -| `soft` | [`theme-soft.css`](../../frontend/src/themes/theme-soft.css) | Warmes Soft Slate mit Indigo-Akzent | Weich gerundet (`12px` Cards, `8px` Buttons) | -| *Template* | [`theme-template.css`](../../frontend/src/themes/theme-template.css) | VollstĂ€ndig dokumentierte Kopiervorlage | Frei anpassbar | +| `dark` | [`theme-dark.css`](../frontend/src/themes/theme-dark.css) | Standard Dark Slate / Monochrom | Streng eckig (`0px` Cards, `2px` Buttons) | +| `light` | [`theme-light.css`](../frontend/src/themes/theme-light.css) | Daylight Clean White / Minimalistisch | Modern dezent (`4px` Cards, `4px` Buttons) | +| `high-contrast` | [`theme-high-contrast.css`](../frontend/src/themes/theme-high-contrast.css) | Reines Schwarz/Weiß mit AAA-Kontrast | Streng eckig (`0px`, krĂ€ftige Rahmen) | +| `soft` | [`theme-soft.css`](../frontend/src/themes/theme-soft.css) | Warmes Soft Slate mit Indigo-Akzent | Weich gerundet (`12px` Cards, `8px` Buttons) | +| *Template* | [`theme-template.css`](../frontend/src/themes/theme-template.css) | VollstĂ€ndig dokumentierte Kopiervorlage | Frei anpassbar | --- ## 🔌 4. Tailwind-Bridge & Semantische Klassen -In [`frontend/src/index.css`](../../frontend/src/index.css) und `tailwind.config.js` sind die semantischen Utility-Klassen definiert, die sich automatisch an das gewĂ€hlte Theme anpassen: +In [`frontend/src/index.css`](../frontend/src/index.css) und `tailwind.config.js` sind die semantischen Utility-Klassen definiert, die sich automatisch an das gewĂ€hlte Theme anpassen: - `.panel-surface`: Wendet `--bg-panel`, `--border-color` und `--radius-card` an. - `.input-field`: Wendet `--bg-input`, `--border-color`, `--radius-input` und `--text-main` an. @@ -105,5 +105,5 @@ Um ein neues Theme (z.B. „Nordic Blue" oder „Cyberpunk") hinzuzufĂŒgen: 1. Kopiere `frontend/src/themes/theme-template.css` nach `frontend/src/themes/theme-meintheme.css`. 2. Ändere den Selektor `:root[data-theme="template"]` in `:root[data-theme="meintheme"]`. 3. Passe Farben und Rundungen nach Belieben an. -4. Importiere die CSS-Datei in [`frontend/src/lib/theme.ts`](../../frontend/src/lib/theme.ts) und trage das Theme im `AVAILABLE_THEMES`-Array ein. +4. Importiere die CSS-Datei in [`frontend/src/lib/theme.ts`](../frontend/src/lib/theme.ts) und trage das Theme im `AVAILABLE_THEMES`-Array ein. 5. Das neue Theme steht sofort in den Kontoeinstellungen und im Profil-Dropdown zur VerfĂŒgung! diff --git a/README.md b/README.md index 8ef9705..75c6a53 100644 --- a/README.md +++ b/README.md @@ -5,236 +5,194 @@ [![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart) -[![Markdown](https://img.shields.io/badge/Datenhoheit-Pure%20Markdown%20%26%20Outliner-18181b?style=flat-square)](#) -[![License](https://img.shields.io/badge/Lizenz-MIT-22c55e?style=flat-square)](LICENSE) +[![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir) -**MindSift** ist deine zentrale, kompromisslos schnelle Schaltstelle fĂŒr spontane Gedanken, strukturierte Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen mit Zoom-Fokus und gemeinsame Echtzeit-Listen — ablenkungsfrei gestaltet, lokal als Standard-Markdown gespeichert und zu 100 % unter deiner eigenen Kontrolle. - -
+**MindSift** ist eine selbst gehostete Schaltstelle fĂŒr spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen und gemeinsame Listen. Alles lĂ€uft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server.

- MindSift Triage Desktop + MindSift Triage Desktop

-[Philosophie](#-die-philosophie) ‱ [Funktionen](#-was-mindsift-auszeichnet) ‱ [Schnellstart](#-schnellstart) ‱ [FĂŒr wen?](#-fĂŒr-wen-ist-mindsift-gemacht) +[Philosophie](#-die-philosophie) ‱ [Funktionen](#-funktionen) ‱ [Schnellstart](#-schnellstart) ‱ [Betrieb](#-betrieb--konfiguration) ‱ [Entwicklung](#-entwicklung) ‱ [Dokumentation](#-dokumentation) --- -## 💡 Die Philosophie: *Erst raus aus dem Kopf – spĂ€ter sortieren* +## 💡 Die Philosophie -Klassische ProduktivitĂ€ts- und Notiz-Apps scheitern im Alltag oft an derselben HĂŒrde: **Sie verlangen Struktur im falschen Moment.** -Wenn dir mitten im GesprĂ€ch, unterwegs auf dem Smartphone oder kurz vor Feierabend ein wichtiger Gedanke kommt, willst du keinen Ordner suchen, keine Datenbankfelder ausfĂŒllen und keine starren Formulare bedienen. Du willst den Gedanken in drei Sekunden festhalten – und sicher sein, dass er verlĂ€sslich da ist, wenn du Zeit hast. +Die meisten Notiz- und Aufgaben-Apps verlangen Struktur im falschen Moment: Wenn dir ein Gedanke kommt, willst du keinen Ordner suchen und keine Felder ausfĂŒllen. MindSift trennt deshalb **Erfassen** und **Organisieren**: -**MindSift trennt das Erfassen radikal vom Organisieren:** - -1. **Sofort abladen (Quick Dump):** In die globale Eingabeleiste tippen, `Enter` drĂŒcken, Kopf wieder frei. Du kannst Gedanken aber auch direkt beim Eintippen gezielt einer Aufgabenliste, Einkaufsliste oder Notiz zuweisen. -2. **Spielerisch sichten (Triage):** In ruhigen Momenten sichtest du deinen Stapel Karte fĂŒr Karte – am Desktop per TastaturkĂŒrzel oder auf dem Smartphone per Wischgeste. Mit einer Taste nach links verwerfen, auf „SpĂ€ter“ vertagen oder direkt in eine Aufgabe, Notiz oder Liste umwandeln. -3. **Strukturiert vertiefen:** Aus losen Ideen werden vernetzte Outliner-Dokumente, hierarchische Unteraufgaben im Kanban-Board oder geteilte Listen fĂŒr Familie, WG und Team. +1. **Abladen:** In die Eingabeleiste tippen, `Enter`, fertig. Wer will, weist den Gedanken gleich einer Aufgabe, Liste oder Notiz zu. +2. **Sichten (Triage):** SpĂ€ter gehst du den Stapel Karte fĂŒr Karte durch: verwerfen, zur Aufgabe machen, in eine Notiz oder Liste verwandeln. +3. **Vertiefen:** Aus losen Ideen werden Aufgaben mit Unteraufgaben, vernetzte Outliner-Notizen oder geteilte Listen fĂŒr Familie und Team. --- -## ✹ Was MindSift auszeichnet +## ✹ Funktionen -### ⚡ Quick-Dump & Triage-Deck (Das 3-Sekunden-Prinzip) +### ⚡ Schnellerfassung & Triage -* **Blitz-Erfassung:** Die Schnelleingabeleiste sitzt prominent im Header – jederzeit blitzschnell per Mausklick oder Tastendruck (`C` oder `/`) fokussierbar. -* **Tastatur-Triage am Desktop:** - * `H` oder `←`: Wegwerfen / Erledigt - * `L` oder `→`: Sofort zur Aufgabe machen - * `T`: Termin-Aufgabe mit FĂ€lligkeitsdatum - * `D`: In Workspace-Notiz umwandeln - * `Leertaste`: SpĂ€ter (ans Ende des Stapels schieben) -* **Mobile-First & Touch-optimiert:** Unterwegs kannst du Gedanken per Wischgeste (Swipe nach links zum Verwerfen, Swipe nach rechts zum Behalten) sortieren. +* Eingabeleiste im Kopf der App, jederzeit mit `C` oder `/` fokussierbar; auch per Spracheingabe (Browser-Funktion). +* Ziel beim Erfassen wĂ€hlbar: Posteingang, Aufgabe, Listeneintrag oder Notiz. +* **Triage-Deck:** `H`/`←` verwerfen, `L`/`→` zur Aufgabe, `T` Aufgabe mit Termin, `D` in Notiz verwandeln, Leertaste = spĂ€ter. Auf dem Smartphone per Wischgeste. +* Papierkorb mit Wiederherstellen.

- MindSift Mobile Triage + MindSift Mobile Triage

---- +### ✅ Aufgaben & Kanban -### 📝 Workspace: Der hierarchische Outliner mit Zoom-Fokus - -Der MindSift-Workspace vereint die Übersicht eines klassischen Dokumenten-Explorers mit der Leichtigkeit eines hierarchischen Outliners (inspiriert von Logseq und Roam). +* Listen- und Kanban-Ansicht, Spalten pro Aufgabenliste frei konfigurierbar. +* PrioritĂ€ten, FĂ€lligkeitsdatum, Hashtags, Unteraufgaben mit Fortschrittsbalken, Markdown-Notizen. +* Filter, Gruppierung und Sortierung; Drag & Drop in Liste und Board. +* **Aufgabenlisten teilen** mit Benutzern oder Gruppen, wahlweise als **Bearbeiter** oder **nur lesend**. +* **Alarme** bei FĂ€lligkeit per E-Mail, Discord oder eigenem Webhook.

- MindSift Workspace Outliner + MindSift Aufgaben und Kanban-Board

-* **Jeder Gedanke ein Bullet-Point:** - * `Enter`: Neuer Bullet-Point auf derselben Ebene - * `Tab` / `Shift + Tab`: Unterordnen (einrĂŒcken) und ausrĂŒcken - * `Shift + Enter`: Weicher Zeilenumbruch innerhalb desselben Blocks -* **🔍 Fokus-Modus & Zoom-in (Hoisting):** - * Klicke auf einen Bullet-Punkt (`‱`), um **isoliert in diesen Ast hineinzuzoomen**. - * Der gewĂ€hlte Punkt wird zur prominenten Überschrift, sodass du dich ohne jede Ablenkung auf genau dieses Thema konzentrieren kannst. - * Eine klickbare Breadcrumb-Leiste oben zeigt deinen aktuellen Pfad und fĂŒhrt dich mit einem Klick zurĂŒck in ĂŒbergeordnete Ebenen oder ins Hauptdokument. -* **Intuitives Drag & Drop fĂŒr Bullet-Points:** - * Verschiebe AbsĂ€tze und ganze GedankenbĂ€ume mĂŒhelos mit der Maus. - * **Unterpunkte per Drop erstellen:** Ziehst du einen Bullet-Point direkt auf einen anderen AufzĂ€hlungspunkt, wird er automatisch als Unter-Bullet eingerĂŒckt. Eingeklappte Eltern-Punkte klappen beim Hineinlegen automatisch auf. - * Untergeordnete Unterpunkte werden beim Ziehen automatisch als zusammenhĂ€ngender Ast mitbewegt. -* **Live-Kollaboration & Presence-Tracking:** - * Gleichzeitiges, latenzfreies Bearbeiten von Dokumenten im Team ohne blockierende Sperren. - * Farbige Avatare im Header und Live-Cursors am aktiven Block visualisieren genau, wer gerade wo tippt. - * Strikter Echo-Schutz verhindert Cursor-Springen und Flackern beim Tippen. -* **Slash-Befehle (`/`):** Tippe einfach `/` fĂŒr `/todo`, `/link` (Notizen & Web-Links verknĂŒpfen), `/h1`, `/h2`, `/h3`, `/code`, `/callout`, `/quote` oder `/divider`. -* **Vernetzung & Hashtags:** Verlinke Notizen untereinander mit Wiki-Links (`[[Notiztitel]]`) und tagge Gedanken mit `#Hashtags`. -* **Explorer mit Ordnern & Favoriten:** Organisiere Dokumente hierarchisch in Ordnern, verschiebe Notizen per Drag & Drop und hefte deine wichtigsten Dokumente als Favoriten ganz oben an. -* **100 % Pure-DB & Markdown-Export:** Alle Notizen und Blöcke werden transaktional und sicher in SQLite gespeichert. Beim Speichern wird automatisch ein sauberer Markdown-Cache generiert, und Dokumente können jederzeit mit einem Klick als `.md`-Datei exportiert werden. +### 📝 Workspace: Outliner-Notizen ---- - -### ✅ Aufgabenlisten & Kanban-Board - -Behalte offene Aufgaben, Projekte und Termine im Blick – wahlweise als strukturierte Liste oder als visuelles Kanban-Board. +* Jeder Gedanke ein Bullet: `Enter`, `Tab`/`Shift+Tab`, Ein-/Ausklappen, Drag & Drop ganzer Äste. +* **Zoom-Fokus** per Klick auf den Bullet, Breadcrumbs zum ZurĂŒcknavigieren. +* Blocktypen und Slash-Befehle: `/todo`, `/link`, `/h1`–`/h3`, `/code` (mit Syntax-Hervorhebung), `/callout`, `/divider`. +* Wiki-Links `[[Notiz]]`, Links und `#Hashtags`; Tag-Seiten bĂŒndeln alle Fundstellen. +* Ordner, Favoriten, **To-Do-Rollup** ĂŒber alle Notizen und Export einer Notiz als `.md`. +* EinfĂŒgen von mehrzeiligem Markdown wird in Blöcke zerlegt. +* **Teilen:** Notizen und ganze Ordner lassen sich Benutzern oder Gruppen **zum Lesen** freigeben. EmpfĂ€nger sortieren sie in ihren eigenen Baum ein.

- MindSift Aufgaben und Kanban-Board + MindSift Workspace Outliner

-* **Flexible Ansichten (Liste & Kanban):** Wechsle mit einem Klick zwischen kompakter Listenansicht und dem interaktiven Kanban-Board (*Zu erledigen*, *In Arbeit*, *Wartend*, *Erledigt*). -* **Unteraufgaben (Subtasks):** - * Gliedere komplexe Aufgaben in beliebig viele Teilaufgaben. - * Ein visueller Fortschrittsbalken und eine Prozentanzeige zeigen dir auf einen Blick, wie weit die Aufgabe gediehen ist. -* **Freies Drag & Drop:** - * Sortiere Aufgaben vertikal in deiner bevorzugten Reihenfolge. - * Im Kanban-Board verschiebst du Karten per Drag & Drop direkt zwischen Status-Spalten oder ordnest sie innerhalb einer Spalte um. - * Kein mĂŒhsames Suchen nach Greifern: Klicke und ziehe eine Aufgabe einfach direkt mit der Maus – ein kurzer Klick ohne Ziehen öffnet das Detail-Modal zum Bearbeiten. -* **PrioritĂ€ten & Termine:** Hebe dringende oder wichtige Aufgaben mit Farbcodes und Icons hervor (đŸ”„ Dringend, ⚠ Wichtig). Setze FĂ€lligkeitsdaten und filtere nach Hashtags. -* **Geteilte Aufgabenlisten:** Erstelle separate Listen fĂŒr Beruf, Homelab oder Familie und teile sie gezielt mit Gruppen, Teams oder einzelnen Nutzern. +### 🛒 Geteilte Listen ---- - -### 🛒 Geteilte Listen (Einkauf & Checklisten) - -Ob Wochenmarkt, Baumarkt oder Urlaubs-Packliste – erstelle geteilte Listen, die fĂŒr gemeinsames Arbeiten in Echtzeit optimiert sind. +* Einkaufs-, Pack- und Checklisten mit Kategorien per Hashtag (`Hafermilch #KĂŒhlregal`), Mengenangaben und Drag & Drop zwischen Kategorien. +* Freigabe an Benutzer und Gruppen (Bearbeiter oder Leser); abgehakte EintrĂ€ge erscheinen bei allen in Echtzeit.

- MindSift Geteilte Listen + MindSift Geteilte Listen

-* **Automatische Hashtag-Kategorien:** - * Tagge Artikel einfach mit einem Hashtag (z. B. `Hafermilch #KĂŒhlregal` oder `Bananen #Obst`). - * MindSift sortiert die Kategorien automatisch alphabetisch und zeigt sie in ĂŒbersichtlichen Sektionen mit Kacheln an. - * **Hashtag-AutovervollstĂ€ndigung:** Bereits beim Tippen eines `#` schlĂ€gt dir die Eingabe existierende Hashtags vor (case-insensitive) – mit Tastaturnavigation und Sofort-Übernahme per `Enter`. -* **Mengenangaben & Schnell-Eingabe:** Erfasse Mengen (`2x`, `500g`) direkt im Eingabefeld. -* **Drag & Drop zwischen Kategorien:** Ziehe EintrĂ€ge mit der Maus nach oben oder unten oder schiebe sie direkt in einen anderen Hashtag-Bereich – der Hashtag im Text wird automatisch aktualisiert. -* **Gemeinsam teilen mit Gruppen & Teams:** - * Über das einheitliche Teilen-Modal kannst du jede Liste mit deinen Teams, Arbeitsgruppen oder Einzelpersonen freigeben. - * Avatare und Freigabe-Badges im Header zeigen dir transparent, wer Zugriff hat. -* **Echtzeit-WebSocket-Synchronisation:** Wird ein Artikel im Supermarkt abgehakt, hakt er sich bei allen Beteiligten im selben Augenblick live ab. -* **Eckiges Abhaken & AufrĂ€umen:** Erledigte Punkte wandern in einen separaten Bereich und können mit einem Klick aufgerĂ€umt werden. +### 🏱 Benutzer, Gruppen, Administration ---- +* GefĂŒhrte Ersteinrichtung, Rollen *Admin* und *Mitglied*, Einladungscodes, optional offene Registrierung. +* Gruppen mit Gruppen-Admins fĂŒr Freigaben. +* Admin-Bereich: Benutzer, Einladungen, Instanzname, SMTP, Freigabe-Modus. +* Vier Themes (Dark, Light, High-Contrast, Soft), installierbar als PWA. -### 📋 Globale To-Dos: Das automatische Notizen-Rollup +### 🔌 Integrationen -* **Automatische Erfassung:** MindSift durchsucht alle Workspace-Notizen und sammelt sĂ€mtliche Checkboxen (`- [ ]`) in einem zentralen Rollup-Dashboard. -* **Ordner-Filter:** Isoliere Aufgaben bestimmter Projekte mit einem Klick. -* **2-Wege-Echtzeit-Sync:** Hakst du ein To-Do im Rollup ab, wird die physische Markdown-Datei auf der Festplatte augenblicklich als `- [x]` aktualisiert. - ---- - -### 🏱 Benutzer, Gruppen & Administration - -* **GefĂŒhrte Ersteinrichtung:** Beim ersten Start fĂŒhrt dich ein Assistent durch die Einrichtung deines Administrator-Kontos. -* **EigenstĂ€ndiger Gruppen-Bereich:** Verwalte eigene Gruppen und Teams direkt ĂŒber das Benutzer-MenĂŒ oben rechts. -* **Gruppen-Administration:** Wer eine Gruppe anlegt oder zum Gruppen-Admin ernannt wird, hat die volle Kontrolle ĂŒber die Mitglieder dieser Gruppe. -* **Server-Sicherheit:** Schalte die öffentliche Registrierung ab und lade neue Mitglieder kontrolliert per Einladungscode ein. +* **Webhook / API-Key:** `POST /api/v1/webhooks/ingest` legt Aufgaben aus Smarthome, Skripten oder Automationen an. +* Persönliche API-Keys, Datenexport als JSON. --- ## 🚀 Schnellstart -MindSift lĂ€uft als leichtgewichtiger, einzelner Container und bringt Webserver, API, SQLite-Datenbank und Frontend direkt mit. +MindSift lĂ€uft als einzelner Container mit Webserver, API, Datenbank und Frontend. -### Option 1: Der 1-Zeiler (Empfohlen) - -FĂŒhre diesen Befehl im Terminal deines Servers aus. Er erstellt das Verzeichnis, lĂ€dt die Konfiguration, generiert ein sicheres JWT-Geheimnis und startet den Container: +### Option 1: Installationsskript ```bash curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/install.sh | bash ``` ---- +Das Skript legt ein Verzeichnis an, lĂ€dt `docker-compose.yml` und `.env.example`, erzeugt ein `JWT_SECRET` und startet den Container. -### Option 2: Schneller Start mit zwei `curl`-Befehlen - -Falls du die Dateien direkt ohne Skript herunterladen möchtest: +### Option 2: Docker Compose ```bash mkdir -p mindsift && cd mindsift curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/docker-compose.yml -o docker-compose.yml curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/.env.example -o .env +# In .env: JWT_SECRET durch einen langen Zufallswert ersetzen! docker compose up -d ``` ---- - -### Option 3: Manuelle `docker-compose.yml` - -Erstelle eine Datei namens `docker-compose.yml`: +Minimal: ```yaml services: mindsift: image: git.dsit.me/sandrodach/mindsift:dev - container_name: mindsift restart: unless-stopped - ports: - - "8080:8080" - volumes: - - ./data:/data + ports: ["8080:8080"] + volumes: ["./data:/data"] environment: - - PORT=8080 - - DATA_DIR=/data - - JWT_SECRET=hier-ein-langes-sicheres-zufalls-geheimnis-eintragen - - ALLOW_REGISTRATION=false - # Optional: Mailserver fĂŒr E-Mail-Alarme (auch im Admin-UI einstellbar) - - SMTP_HOST= - - SMTP_PORT=587 - - SMTP_USER= - - SMTP_PASS= - - SMTP_FROM= - - SMTP_TLS=true + - JWT_SECRET=hier-ein-langer-zufaelliger-wert + - BASE_URL=https://mindsift.example.com ``` -Starte den Container: +### Ersteinrichtung + +Rufe `http://localhost:8080` auf. Beim ersten Start fĂŒhrt ein Assistent durch das Anlegen des Administrator-Kontos. + +> ⚠ **Setze `JWT_SECRET`.** Ohne Angabe nutzt der Server einen öffentlich bekannten Standardwert, mit dem sich Anmelde-Tokens fĂ€lschen lassen. Rufe die Instanz außerdem direkt nach dem Start auf, damit kein Fremder das erste Konto anlegt. + +--- + +## ⚙ Betrieb & Konfiguration + +| Variable | Standard | Zweck | +|---|---|---| +| `PORT` | `8080` | HTTP-Port | +| `DATA_DIR` | `/data` (Container) | Ablage der Datenbank `mindsift.db` | +| `JWT_SECRET` | unsicherer Standard | **Pflicht in Produktion**, SignaturschlĂŒssel | +| `BASE_URL` | `http://localhost:8080` | Öffentliche URL | +| `ALLOW_REGISTRATION` | `false` | Offene Registrierung (im Admin-UI Ă€nderbar) | +| `LOG_LEVEL` / `DEBUG` | `info` / `false` | Logging | +| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`, `SMTP_TLS` | leer | E-Mail-Alarme (auch im Admin-UI) | +| `MINDA_DEMO` | `false` | Legt Demo-Konten `admin/admin`, `user/user` an. **Nie auf öffentlichen Servern!** | + +* **Reverse-Proxy:** WebSocket-Upgrade und ungepufferte SSE-Streams mĂŒssen durchgereicht werden. Beispiele fĂŒr Nginx, Traefik, Caddy und Cloudflare: [Doku/reverse-proxy.md](Doku/reverse-proxy.md). +* **Datensicherung:** Alle Daten liegen in `data/mindsift.db` (+ `-wal`/`-shm`). Sichere sie mit `sqlite3 mindsift.db ".backup 'backup.db'"` oder bei gestopptem Container. ZusĂ€tzlich gibt es einen JSON-Export pro Benutzer. +* **Updates:** Neues Image ziehen und Container neu starten (`docker compose pull && docker compose up -d`). Das Schema wird beim Start automatisch angepasst. + +--- + +## 🛠 Entwicklung + +Voraussetzungen: Go 1.23, Node 22. ```bash -docker compose up -d +# Backend: Vet und Tests +go vet ./... && go test ./... + +# Frontend +cd frontend +npm ci +npm run dev # Vite-Entwicklungsserver +npx tsc --noEmit # TypprĂŒfung +npm run build # Produktions-Build ``` ---- +FĂŒr das Binary wird das gebaute Frontend nach `cmd/server/dist` kopiert (`go:embed`); das mehrstufige `Dockerfile` erledigt das und fĂŒhrt Vet und Tests beim Image-Build aus. -### 🌐 Ersteinrichtung aufrufen - -Öffne deinen Browser unter **`http://localhost:8080`** (bzw. die IP deines Servers). -MindSift erkennt den Erststart automatisch und fĂŒhrt dich durch den Einrichtungs-Assistenten zum Erstellen des Administrator-Kontos. +**Stack:** Go (chi, gorilla/websocket, JWT, modernc SQLite) · React 18, TypeScript, Vite, Tailwind. --- -## đŸ‘„ FĂŒr wen ist MindSift gemacht? +## 📚 Dokumentation -* **FĂŒr Macher & Wissensarbeiter**, die tagsĂŒber dutzende Gedanken, Links und Aufgaben festhalten mĂŒssen, ohne aus dem Arbeitsfluss gerissen zu werden. -* **FĂŒr Outliner-Fans**, die die FlexibilitĂ€t von Logseq schĂ€tzen, aber ihre Notizen in einer klaren Ordnerstruktur auf dem eigenen Server haben wollen. -* **FĂŒr Familien & WGs**, die gemeinsame Einkaufs- und Packlisten ohne Werbe-Apps und fremde Clouds synchronisieren möchten. -* **FĂŒr Homelab- & Self-Hosting-Begeisterte**, die volle Datenhoheit, saubere Markdown-Dateien und automatisierte Webhooks auf eigener Hardware schĂ€tzen. +Die technische Doku liegt unter [Doku/](Doku/README.md): Architektur, Datenbank, REST- und Echtzeit-API, Module, Freigaben und Berechtigungen, Themes. Bekannte Schwachstellen und offene Punkte stehen in [Doku/flaws.md](Doku/flaws.md). --- ## 🔒 Deine Daten gehören dir -* **100 % Self-Hosted:** Keine fremden Cloud-Dienste, keine Telemetrie, kein Tracking. -* **Kein Vendor-Lock-in:** Alle Notizen liegen als relationale Daten in SQLite und werden automatisch als Standard-Markdown kompiliert. Dokumente lassen sich jederzeit mit einem Klick als `.md`-Datei exportieren oder als SQLite-Dump sichern. +* Selbst gehostet, keine Telemetrie, keine Fremddienste. Ausgehende Verbindungen gibt es nur zu den Alarm-Zielen, die du selbst einrichtest (SMTP, Discord, Webhook). +* Alle Inhalte liegen in einer einzigen SQLite-Datei; Notizen lassen sich als Markdown exportieren, alle Daten eines Benutzers als JSON. --- ## 📄 Lizenz -Veröffentlicht unter der **MIT-Lizenz** – siehe [LICENSE](LICENSE). +Im Repository liegt derzeit keine `LICENSE`-Datei; die Lizenz ist damit nicht festgelegt.
-Gebaut fĂŒr maximale Klarheit, Geschwindigkeit und digitale SouverĂ€nitĂ€t. -
\ No newline at end of file +Gebaut fĂŒr Klarheit, Geschwindigkeit und digitale SouverĂ€nitĂ€t. +