fix(docs/storage): resolve FLAW-02, remove unused docDir and update documentation for pure database architecture
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 39s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 39s
This commit is contained in:
1 parent
3480859d8a
commit
0bdeb5f320
7 files changed
+42
-52
No files matched your search
+6
-8
@@ -17,7 +17,7 @@ Doku/
|
|||||||
├── modules/
|
├── modules/
|
||||||
│ ├── triage.md # Modul 1: Triage & Quick-Dump (State Machine & Audio)
|
│ ├── triage.md # Modul 1: Triage & Quick-Dump (State Machine & Audio)
|
||||||
│ ├── tasks.md # Modul 2: Aufgaben & Kanban-System (Alarme & Scheduler)
|
│ ├── tasks.md # Modul 2: Aufgaben & Kanban-System (Alarme & Scheduler)
|
||||||
│ ├── workspace.md # Modul 3: Workspace Outliner-Engine, Wiki-Links & Disk-Sync
|
│ ├── workspace.md # Modul 3: Workspace Outliner-Engine, Wiki-Links & Pure-DB Storage
|
||||||
│ └── lists.md # Modul 4: Geteilte Listen (Live-Kollaboration & D&D)
|
│ └── lists.md # Modul 4: Geteilte Listen (Live-Kollaboration & D&D)
|
||||||
├── sharing-and-permissions.md # Synchrones Teilen, Rollen, Isolation & Mount-Entkopplung
|
├── sharing-and-permissions.md # Synchrones Teilen, Rollen, Isolation & Mount-Entkopplung
|
||||||
├── theming.md # Single-File CSS Theme-Engine & Design-System
|
├── theming.md # Single-File CSS Theme-Engine & Design-System
|
||||||
@@ -38,7 +38,7 @@ Doku/
|
|||||||
6. **[Kernmodul: Aufgaben & Kanban-System](modules/tasks.md)**
|
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).
|
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)**
|
7. **[Kernmodul: Workspace & Outliner-Engine](modules/workspace.md)**
|
||||||
Logseq-inspirierte hierarchische Block-Engine, Zoom-Hoisting, Wiki-Links, Disk-Mirroring (`.md`) und Globales To-Do Rollup.
|
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)**
|
8. **[Kernmodul: Geteilte Listen & Kollaboration](modules/lists.md)**
|
||||||
Listen-Typen, Mengen, Kategorien, Live-Checkoff und Drag & Drop Sortierung.
|
Listen-Typen, Mengen, Kategorien, Live-Checkoff und Drag & Drop Sortierung.
|
||||||
9. **[Synchrones Teilen & Berechtigungskonzept](sharing-and-permissions.md)**
|
9. **[Synchrones Teilen & Berechtigungskonzept](sharing-and-permissions.md)**
|
||||||
@@ -46,7 +46,7 @@ Doku/
|
|||||||
10. **[Single-File Theming & Design-System](theming.md)**
|
10. **[Single-File Theming & Design-System](theming.md)**
|
||||||
1-Datei = 1-Theme Philosophie, die 6 CSS-Variablen-Blöcke, Semantische CSS-Klassen und Geometrie-Steuerung.
|
1-Datei = 1-Theme Philosophie, die 6 CSS-Variablen-Blöcke, Semantische CSS-Klassen und Geometrie-Steuerung.
|
||||||
11. **[Technische Schwachstellen & Optimierungspotenziale (Flaws)](flaws.md)**
|
11. **[Technische Schwachstellen & Optimierungspotenziale (Flaws)](flaws.md)**
|
||||||
Kritische Analyse von Concurrency-Grenzen, Dual-Storage-Risiken, Undo/Redo, Volltextsuche und Skalierungs-Engpässen.
|
Kritische Analyse von Concurrency-Grenzen, WebSocket-Proxies, Undo/Redo, Volltextsuche und Skalierungs-Engpässen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -68,12 +68,11 @@ graph TB
|
|||||||
ChiRouter["Chi Router (REST API)"]
|
ChiRouter["Chi Router (REST API)"]
|
||||||
WSHub["Gorilla WebSocket Hub & Lock Manager"]
|
WSHub["Gorilla WebSocket Hub & Lock Manager"]
|
||||||
AlarmTicker["Alarm Scheduler (Background Ticker)"]
|
AlarmTicker["Alarm Scheduler (Background Ticker)"]
|
||||||
DiskSync["Disk Mirror Engine (/data/workspace)"]
|
StorageEngine["Storage Layer (Domain & Repositories)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Storage ["Storage Layer"]
|
subgraph Storage ["Storage Layer"]
|
||||||
SQLite[("SQLite WAL Database (/data/mindsift.db)")]
|
SQLite[("SQLite WAL Database (/data/mindsift.db)")]
|
||||||
DiskFiles[("Physische Markdown Files (/data/workspace)")]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph External ["Externe Schnittstellen"]
|
subgraph External ["Externe Schnittstellen"]
|
||||||
@@ -86,9 +85,8 @@ graph TB
|
|||||||
WSClient -->|WSS Realtime| Traefik
|
WSClient -->|WSS Realtime| Traefik
|
||||||
Traefik --> ChiRouter
|
Traefik --> ChiRouter
|
||||||
Traefik --> WSHub
|
Traefik --> WSHub
|
||||||
ChiRouter --> SQLite
|
ChiRouter --> StorageEngine
|
||||||
ChiRouter --> DiskSync
|
StorageEngine --> SQLite
|
||||||
DiskSync --> DiskFiles
|
|
||||||
AlarmTicker --> Discord
|
AlarmTicker --> Discord
|
||||||
AlarmTicker --> SMTPServer
|
AlarmTicker --> SMTPServer
|
||||||
HomeAssistant -->|POST /api/v1/inbox| Traefik
|
HomeAssistant -->|POST /api/v1/inbox| Traefik
|
||||||
|
|||||||
@@ -138,5 +138,5 @@ MindSift wird als schlanker, autarker Docker-Container ausgeliefert:
|
|||||||
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.
|
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`:
|
Alle persistenten Daten befinden sich in einem einzigen Verzeichnis `/data`:
|
||||||
- `/data/mindsift.db` (SQLite Hauptdatenbank)
|
- `/data/mindsift.db` (SQLite Hauptdatenbank inklusive WAL-Journal und Shared-Memory-Dateien)
|
||||||
- `/data/workspace/` (Physische Markdown-Dateien der Workspace-Notizen)
|
- Notizen und Workspace-Blöcke werden zu 100% in der SQLite-Datenbank persistiert (keine losen Markdown-Dateien auf der Festplatte).
|
||||||
+2
-2
@@ -208,7 +208,7 @@ CREATE TABLE IF NOT EXISTS task_list_shares (
|
|||||||
### 2.4 Workspace: Dokumente, Blöcke & Geteilte Ordner
|
### 2.4 Workspace: Dokumente, Blöcke & Geteilte Ordner
|
||||||
|
|
||||||
#### `documents` & `document_blocks` (Database-First Outliner)
|
#### `documents` & `document_blocks` (Database-First Outliner)
|
||||||
Dokumente werden als Kopfdatensatz in `documents` gespeichert, während der Inhalt als feingranulare Blöcke in `document_blocks` verwaltet wird. Zusätzlich spiegelt MindSift die Daten synchron als physische `.md`-Datei auf Festplatte.
|
Dokumente werden als Kopfdatensatz in `documents` gespeichert, während der Inhalt als feingranulare Blöcke in `document_blocks` verwaltet wird. MindSift speichert alle Notizen zu 100% in SQLite (keine physischen `.md`-Dateien auf Festplatte).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE IF NOT EXISTS documents (
|
CREATE TABLE IF NOT EXISTS documents (
|
||||||
@@ -216,7 +216,7 @@ CREATE TABLE IF NOT EXISTS documents (
|
|||||||
user_id TEXT NOT NULL, -- Eigentümer
|
user_id TEXT NOT NULL, -- Eigentümer
|
||||||
title TEXT NOT NULL, -- Dokumenttitel
|
title TEXT NOT NULL, -- Dokumenttitel
|
||||||
icon TEXT NOT NULL DEFAULT '📄', -- Emoji-Icon
|
icon TEXT NOT NULL DEFAULT '📄', -- Emoji-Icon
|
||||||
content TEXT NOT NULL DEFAULT '', -- Markdown-Volltext (Spiegelung)
|
content TEXT NOT NULL DEFAULT '', -- Plaintext / Text-Repräsentation (Cache)
|
||||||
blocks_json TEXT NOT NULL DEFAULT '[]', -- Legacy JSON-Cache
|
blocks_json TEXT NOT NULL DEFAULT '[]', -- Legacy JSON-Cache
|
||||||
tags TEXT NOT NULL DEFAULT '[]', -- JSON Array von Tags
|
tags TEXT NOT NULL DEFAULT '[]', -- JSON Array von Tags
|
||||||
folder_path TEXT NOT NULL DEFAULT '', -- Relativer Ordnerpfad des Eigentümers
|
folder_path TEXT NOT NULL DEFAULT '', -- Relativer Ordnerpfad des Eigentümers
|
||||||
|
|||||||
+10
-9
@@ -9,7 +9,7 @@ Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse alle
|
|||||||
| ID | Bereich | Problem / Schwachstelle | Schweregrad | Risiko im Produktivbetrieb |
|
| ID | Bereich | Problem / Schwachstelle | Schweregrad | Risiko im Produktivbetrieb |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **FLAW-01** | Workspace Sync | Kein echtes CRDT/OT – Lock-Heartbeat-Modell mit Last-Write-Wins | **Kritisch** | Zeilenverlust bei gleichzeitigen Bearbeitungen oder Netzschwankungen |
|
| **FLAW-01** | Workspace Sync | Kein echtes CRDT/OT – Lock-Heartbeat-Modell mit Last-Write-Wins | **Kritisch** | Zeilenverlust bei gleichzeitigen Bearbeitungen oder Netzschwankungen |
|
||||||
| **FLAW-02** | Storage | Dual-Storage Drift (SQLite `document_blocks` vs. Dateisystem `.md`) | **Hoch** | Externe Änderungen an `.md` werden nicht automatisch in SQLite gespiegelt |
|
| **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 | **Mittel** | Verzögerte Synchronisierung (1–3s Polling) in Firmennetzwerken |
|
| **FLAW-03** | Netzwerk / WS | Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback | **Mittel** | Verzögerte Synchronisierung (1–3s Polling) in Firmennetzwerken |
|
||||||
| **FLAW-04** | Datenbank / Suche | Volltextsuche via `LIKE '%query%'` statt SQLite FTS5 | **Mittel** | Performance-Einbrüche bei wachsenden Notizbeständen (>10.000 Blöcke) |
|
| **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-05** | Frontend DOM | Fehlende Virtualisierung im Outliner-Baum | **Mittel** | Frame-Drops und Lags bei Dokumenten mit >500 Blöcken auf Mobilgeräten |
|
||||||
@@ -24,7 +24,7 @@ Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse alle
|
|||||||
## 🔍 Detaillierte Analyse & Maßnahmen
|
## 🔍 Detaillierte Analyse & Maßnahmen
|
||||||
|
|
||||||
### FLAW-01: Kein echtes CRDT/OT – Lock-Heartbeat & Last-Write-Wins
|
### FLAW-01: Kein echtes CRDT/OT – Lock-Heartbeat & Last-Write-Wins
|
||||||
- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), [`modules/workspace.md`](./modules/workspace.md), `internal/ws/hub.go`, `frontend/src/lib/components/Outliner.svelte`
|
- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), [`modules/workspace.md`](./modules/workspace.md), `internal/ws/hub.go`, `frontend/src/features/workspace/WorkspaceView.tsx`
|
||||||
- **Ursache:** MindSift nutzt zur Vermeidung von Konflikten eine kooperative Bearbeitungssperre (*Document Lease Lock*, 30 Sekunden TTL mit Heartbeat). Wenn zwei Nutzer dasselbe Dokument geöffnet haben, blockiert der aktive Schreiber das Bearbeiten für andere. Verliert ein Nutzer jedoch kurzzeitig die WebSocket-Verbindung oder tippt während des Übergangs zum Polling-Fallback, werden Änderungen per REST via Last-Write-Wins (`PUT /api/v1/documents/:id`) gespeichert.
|
- **Ursache:** MindSift nutzt zur Vermeidung von Konflikten eine kooperative Bearbeitungssperre (*Document Lease Lock*, 30 Sekunden TTL mit Heartbeat). Wenn zwei Nutzer dasselbe Dokument geöffnet haben, blockiert der aktive Schreiber das Bearbeiten für andere. Verliert ein Nutzer jedoch kurzzeitig die WebSocket-Verbindung oder tippt während des Übergangs zum Polling-Fallback, werden Änderungen per REST via Last-Write-Wins (`PUT /api/v1/documents/:id`) gespeichert.
|
||||||
- **Folgen:** Wie in früheren Versionen beobachtet, können bei parallelem Schreiben Zeilen überschrieben werden oder Strukturhierarchien (Parent-IDs) verloren gehen.
|
- **Folgen:** Wie in früheren Versionen beobachtet, können bei parallelem Schreiben Zeilen überschrieben werden oder Strukturhierarchien (Parent-IDs) verloren gehen.
|
||||||
- **Empfohlene Lösung:**
|
- **Empfohlene Lösung:**
|
||||||
@@ -33,13 +33,14 @@ Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse alle
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### FLAW-02: Dual-Storage Drift (SQLite vs. Dateisystem `.md`)
|
### FLAW-02: [OBSOLET / GELÖST] Dual-Storage Drift (SQLite vs. Dateisystem)
|
||||||
- **Betroffene Komponenten:** [`database.md`](./database.md), `internal/storage/sqlite.go`, `internal/storage/markdown_sync.go`
|
- **Status:** **Entkräftet & Behoben (Pure Database Architecture)**
|
||||||
- **Ursache:** MindSift pflegt Notizen primär in SQLite (`documents`, `document_blocks`), exportiert bzw. spiegelt sie jedoch als reine `.md`-Dateien auf das Dateisystem (`data/notes/`), um Exportierbarkeit und lokale Lesbarkeit zu gewährleisten.
|
- **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`).
|
||||||
- **Problem:** Es existiert kein systemweiter File-Watcher ([fsnotify](https://github.com/fsnotify/fsnotify)). Bearbeitet der Nutzer eine Notizdatei direkt auf dem Host (z. B. via Obsidian, VS Code, Git-Sync oder SSH), bekommt die SQLite-Datenbank dies nicht mit. Beim nächsten Web-Edit überschreibt SQLite die Datei auf der Festplatte.
|
- **Verifikation:**
|
||||||
- **Empfohlene Lösung:**
|
- Es existieren **keine** Markdown-Dateien auf der Festplatte.
|
||||||
1. Integrieren eines Background-File-Watchers mit `fsnotify` für das Datenverzeichnis.
|
- Das Go-Backend schreibt keine Dateien in ein Notizverzeichnis.
|
||||||
2. Implementieren einer Hash-Prüfung (`sha256` der Datei vs. DB-Metadaten) vor dem Speichern. Bei Abweichung: Nutzerkonflikt-Dialog anzeigen.
|
- 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+20
-17
@@ -1,6 +1,6 @@
|
|||||||
# 📝 Kernmodul 3: Workspace Outliner-Engine, Wiki-Links & Disk-Sync
|
# 📝 Kernmodul 3: Workspace Outliner-Engine, Wiki-Links & Pure-DB Storage
|
||||||
|
|
||||||
Der MindSift Workspace ([`WorkspaceView.tsx`](../../frontend/src/features/workspace/WorkspaceView.tsx)) kombiniert ein **hierarchisches Bullet-Point Notizsystem (inspiriert von Logseq)** mit einem **persönlichen Markdown-Wiki** und einem **globalen To-Do Rollup Dashboard**.
|
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** und einem **globalen To-Do Rollup Dashboard**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,30 +46,33 @@ Der Bullet-Dot dient gleichzeitig als Drag-Anfasser. Wird ein Block verschoben,
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💾 3. Dual-Storage: Database-First & Disk-Mirroring
|
## 💾 3. Pure-Database Architecture (Keine MD-Dateien auf Platte)
|
||||||
|
|
||||||
MindSift löst das Dilemma zwischen schneller relationaler Abfrage und portabler Dateispeicherung durch ein **synchrones Dual-Storage-Modell**:
|
MindSift speichert Notizen und Outliner-Bäume **zu 100% relational in SQLite**. Es werden bewusst **keine Markdown-Dateien auf dem Dateisystem** abgelegt:
|
||||||
|
|
||||||
```
|
```
|
||||||
Client (Block-Editor)
|
Client (Block-Editor)
|
||||||
│ PATCH /api/v1/documents/:id/blocks
|
│ PUT /api/v1/documents/:id/blocks (oder WebSocket)
|
||||||
▼
|
▼
|
||||||
Backend Engine
|
Backend Engine
|
||||||
┌──────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────┐
|
||||||
│ 1. Transaktion in SQLite (document_blocks) │
|
│ Atomare Transaktion in SQLite: │
|
||||||
│ -> Sort-Order, Content, Level, Checked │
|
│ 1. DELETE FROM document_blocks WHERE doc_id │
|
||||||
│ 2. Volltext-Generierung (Markdown-String) │
|
│ 2. Batch INSERT der Blöcke: │
|
||||||
│ 3. Schreiben auf Disk (/data/workspace/...) │
|
│ -> id, doc_id, content, level, checked, │
|
||||||
|
│ sort_order, collapsed │
|
||||||
|
│ 3. WebSocket Broadcast an Raum (doc:id) │
|
||||||
└──────────────────────────────────────────────┘
|
└──────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Präzision & Geschwindigkeit (SQLite):**
|
### Vorteile der reinen Datenbank-Architektur
|
||||||
- Jeder Block besitzt eine eindeutige ID (`blk_...`) und eine Gleitkomma-Reihenfolge (`sort_order REAL`).
|
1. **Kein Dual-Storage & kein Drift:**
|
||||||
- Schnelle Status-Updates (z.B. Checkbox abhaken) schreiben nur genau eine Zeile in die Datenbank.
|
- Keine Inkonsistenzen zwischen Festplattendateien und Datenbankzeilen.
|
||||||
2. **Datensouveränität & Portabilität (Disk-Mirror):**
|
- Keine Dateisystem-Sperren, Race-Conditions oder Berechtigungskonflikte auf Betriebssystemebene.
|
||||||
- Zeitgleich spiegelt der Server das Dokument als Standard-`.md`-Datei unter `/data/workspace/<user_id>/<ordner>/<dateiname>.md`.
|
2. **Atomare Transaktionen (ACID):**
|
||||||
- Einrückungen werden nach dem 2-Leerzeichen-Standard (` - [ ] `) formatiert.
|
- Strukturänderungen (Einrücken, Unterbaum verschieben, Blöcke löschen) sind atomar in einer Transaktion gekapselt.
|
||||||
- Administratoren können das Verzeichnis jederzeit mit externen Werkzeugen (Obsidian, Logseq, Git, rsync) sichern.
|
3. **Skalierbare Abfragen & Rollups:**
|
||||||
|
- Aggregationen (wie das globale To-Do Dashboard oder Tag-Suchen) erfolgen mit hochoptimierten SQL-Abfragen über relationale Indizes, anstatt Hunderte `.md`-Dateien von der Festplatte parsen zu müssen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,4 +82,4 @@ Client (Block-Editor)
|
|||||||
- Scannt in Echtzeit alle unvollendeten `- [ ] ` Checkboxen über alle Ordner und Notizen hinweg.
|
- Scannt in Echtzeit alle unvollendeten `- [ ] ` Checkboxen über alle Ordner und Notizen hinweg.
|
||||||
- Übersichtliche Gruppierung nach **Ordner ➔ Dokument ➔ Aufgabe**.
|
- Übersichtliche Gruppierung nach **Ordner ➔ Dokument ➔ Aufgabe**.
|
||||||
- **Ordner-Filter:** Klick auf einen Ordner isoliert nur dessen Aufgaben für fokussiertes Arbeiten.
|
- **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 die physische `.md`-Datei synchronisiert.
|
- **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.
|
||||||
@@ -21,19 +21,15 @@ import (
|
|||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
docDir string
|
|
||||||
initMu sync.Mutex
|
initMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore(dbPath string) (*Store, error) {
|
func NewStore(dbPath string) (*Store, error) {
|
||||||
var docDir string
|
|
||||||
if dbPath != ":memory:" && !strings.HasPrefix(dbPath, "file:") {
|
if dbPath != ":memory:" && !strings.HasPrefix(dbPath, "file:") {
|
||||||
dir := filepath.Dir(dbPath)
|
dir := filepath.Dir(dbPath)
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create data directory %s: %w", dir, err)
|
return nil, fmt.Errorf("failed to create data directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
docDir = filepath.Join(dir, "workspace")
|
|
||||||
_ = os.MkdirAll(docDir, 0755)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dsn := dbPath
|
dsn := dbPath
|
||||||
@@ -49,7 +45,7 @@ func NewStore(dbPath string) (*Store, error) {
|
|||||||
db.SetMaxOpenConns(8)
|
db.SetMaxOpenConns(8)
|
||||||
db.SetMaxIdleConns(4)
|
db.SetMaxIdleConns(4)
|
||||||
|
|
||||||
store := &Store{db: db, docDir: docDir}
|
store := &Store{db: db}
|
||||||
if err := store.migrate(); err != nil {
|
if err := store.migrate(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -332,7 +331,6 @@ func TestEnsureDemoAccountsAndLoginLookup(t *testing.T) {
|
|||||||
func TestFolderManagementAndDeletion(t *testing.T) {
|
func TestFolderManagementAndDeletion(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tmpDir, "folder_test.db")
|
dbPath := filepath.Join(tmpDir, "folder_test.db")
|
||||||
docDir := filepath.Join(tmpDir, "workspace")
|
|
||||||
|
|
||||||
store, err := NewStore(dbPath)
|
store, err := NewStore(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,7 +343,7 @@ func TestFolderManagementAndDeletion(t *testing.T) {
|
|||||||
t.Fatalf("CreateUser failed: %v", err)
|
t.Fatalf("CreateUser failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Create folders on disk
|
// 1. Create folders
|
||||||
f1, err := store.CreateFolder(user.ID, "Projekte/MindSift")
|
f1, err := store.CreateFolder(user.ID, "Projekte/MindSift")
|
||||||
if err != nil || f1 != "Projekte/MindSift" {
|
if err != nil || f1 != "Projekte/MindSift" {
|
||||||
t.Fatalf("CreateFolder failed: %v, f1=%s", err, f1)
|
t.Fatalf("CreateFolder failed: %v, f1=%s", err, f1)
|
||||||
@@ -401,12 +399,6 @@ func TestFolderManagementAndDeletion(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected doc2 to be deleted")
|
t.Fatalf("expected doc2 to be deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify disk directory was deleted
|
|
||||||
diskFolder := filepath.Join(docDir, user.ID, "Projekte/MindSift")
|
|
||||||
if _, err := os.Stat(diskFolder); !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("expected directory %s to be deleted on disk", diskFolder)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkspaceBlockHashtagsAndDetails(t *testing.T) {
|
func TestWorkspaceBlockHashtagsAndDetails(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user