Files
MindSift/Doku/database.md
T
sandrodachandClaude Opus 5.5 4c26eb2960
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
feat(workspace): link previews above bullets with a web address (M5)
- GET /link-preview?url= fetches the page on the server (https only,
  SSRF-guarded like webhooks), reads Open Graph/Twitter tags, title,
  description and icon from the head, loads picture and icon (no SVG) and
  keeps them in link_previews (migration 6) for 7 days; readers' browsers
  never contact the page. Off in the demo; 300 fetches per address in
  15 minutes.
- Compact card above the bullet (icon, site, title, one line, small
  picture); the duplicate link badge is left out.
- ✕ turns the address into [title](url), so hiding syncs with the note;
  "Link-Vorschauen anzeigen" under appearance switches all off per device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:06:05 +01:00

9.9 KiB

Datenbank

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): 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_…) und 96 Zufallsbits in Hex; ältere Datensätze haben kürzere IDs (40 Bit).

Tabellen

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, organization_id (leer = keine Organisation)
user_invites Einladungscodes code, email, role, organization_id, expires_at, used_by
media Bilder und Audio in Notizen (Migration 5); Datei unter $DATA_DIR/media/<doc_id>/<id>.<ext> id, doc_id, owner_id, kind (image/audio), mime, size, source_url (kopiertes externes Bild), created_at
link_previews Zwischengespeicherte Link-Vorschauen (Migration 6) key (Hash der URL), url, title, description, site_name, image/icon (BLOB, mit *_mime), ok, fetched_at
sessions Angemeldete Geräte (Migration 4, ersetzt revoked_tokens) id (= jti des Tokens), user_id, created_at, last_used_at (höchstens alle 5 Minuten aktualisiert), user_agent, ip, revoked_at (leer = aktiv)
user_themes Eigene CSS-Themes eines Benutzers user_id (FK, ON DELETE CASCADE), name, css_content
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_*

Gedanken und Aufgaben

Tabelle Zweck Wichtige Spalten
password_reset_tokens Links aus „Passwort vergessen?“ token_hash (SHA-256), user_id, created_at, expires_at (1 h), used_at
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 (YYYY-MM-DD ganztägig oder YYYY-MM-DDTHH:MM, Zeitzone des Servers), task_list_id, kanban_status, notes, subtasks (JSON), tags (JSON), alarm_sent, alarm_failures (erfolglose Alarm-Durchläufe, ab 3 aufgegeben), sort_order, source (manual, voice, webhook …)
task_assignees Zuweisungen von Aufgaben task_id (FK thoughts, ON DELETE CASCADE), user_id, created_at; nur Personen mit Zugriff auf die Liste, bereinigt durch PruneTaskAssignees
task_lists Aufgabenlisten user_id (Eigentümer), title, icon, color, columns (JSON, Kanban-Spalten)
task_list_shares Freigaben von Aufgabenlisten target_type (team/user), target_id, permission (editor/viewer, Standard editor)

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='<docId>:<blockId>'.

Workspace

Tabelle Zweck
documents Notizen: title, icon, folder_path, tags (JSON), is_archived (1 = im Papierkorb), archived_at, archived_by (wer gelöscht hat), markdown_cache, shared_folder_id, sub_folder_path, head_seq (letzte Log-Position), title_version, log_floor (bis hierhin ist das Log verdichtet), Altspalten content, blocks_json
blocks Outliner-Blöcke als Baum: document_id, parent_id ('' = oberste Ebene), order_key (Sortierschlüssel unter Geschwistern), type, content, checked, collapsed, lang, version (Textversion), conflict_of/conflict_author (Konfliktkopien), updated_by, deleted (Grabstein) und deleted_at (Zeitpunkt des Löschens, für das Aufräumen). level/position sind Altspalten und werden nicht mehr gepflegt; Ebene und Reihenfolge berechnet der Server beim Lesen aus dem Baum.
doc_ops Änderungs-Log je Notiz: seq (fortlaufend pro Notiz), op_id, user_id, client_id, kind, block_id, payload (Effekt als JSON), created_at. Abspielen aller Einträge ab einem Snapshot ergibt den Stand. Wird verdichtet (siehe unten).
doc_op_results Ergebnis je Client-Operation (op_id eindeutig) für idempotente Wiederholungen; wird nach 90 Tagen gelöscht
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: read = Leser, write = Bearbeiter)
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

Blocktypen: h1, h2, h3, paragraph, text, todo, bullet, code, callout, divider, table, progress.

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), created_by (leer bei alten Einträgen), sort_order

Gruppen

Tabelle Zweck
teams Gruppen der Benutzer: name, description, org_id (Organisation des Erstellers)
team_members team_id, user_id, role (admin/member)
organizations Vom Server-Admin angelegt: name. Mitglieder über users.organization_id (höchstens eine pro Benutzer)

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 ''.

Migrationen und Aufräumarbeiten beim Start

  • 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.
  • Notiz-Synchronisation (migrateDocSync): neue Spalten an blocks (inkl. deleted_at; vorhandene Grabsteine erhalten den Migrationszeitpunkt) und documents (head_seq, title_version, log_floor), Tabellen doc_ops und doc_op_results. Blöcke im alten flachen Format (nur level/position) werden einmalig in den Baum umgewandelt.
  • Standardwerte in system_settings (INSERT OR IGNORE): sharing_mode=organizations, allow_registration=false, instance_name=MindSift, SMTP leer.
  • Migrationen: migrations.go führt nummerierte Schritte genau einmal aus und vermerkt sie in schema_migrations (id, name, applied_at); PRAGMA user_version zeigt die höchste angewandte Nummer. Schritt 3 („Basis-Schema v0.4.1“, migrateBaseline) baut das komplette Schema auf und bringt ältere Datenbanken auf diesen Stand; Spalten werden nur angelegt, wenn sie fehlen. Schlägt ein Schritt fehl, bricht der Start mit der Fehlermeldung ab, statt mit halbem Schema weiterzulaufen. Neue Änderungen kommen als neuer Schritt ans Ende, alte Schritte werden nie verändert. Daten werden beim Start nie gelöscht; nur der Demo-Modus leert die Datenbank.

Sicherung

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).

Verdichtung des Notiz-Logs

Store.CompactDocs läuft beim Start und danach alle 6 Stunden (startCompaction in internal/api/docsync.go):

Was Aufbewahrung Folge
doc_ops geteilter Notizen 30 Tage ältere Einträge werden gelöscht, documents.log_floor merkt sich die Grenze
doc_ops privater Notizen 7 Tage wie oben
Grabsteine in blocks 30 Tage nach deleted_at endgültig gelöscht; das ist selbst ein Log-Eintrag purge, geöffnete Editoren übernehmen ihn ohne Neuladen. Ein Grabstein bleibt, solange ein jüngerer gelöschter Unterblock auf ihn zeigt.
doc_op_results 90 Tage gelöscht

Ein Client, dessen lastSeq unter log_floor liegt (z. B. Gerät wochenlang offline), bekommt von /sync resync: true und lädt den Snapshot neu; seine noch nicht gesendeten Änderungen bleiben in der Warteschlange und werden danach angewendet. Text für einen inzwischen endgültig gelöschten Block wird als neuer Block am Ende der Notiz angelegt, ein neuer Block unter einem endgültig gelöschten Elternblock landet auf der obersten Ebene.