chore(db): stop wiping databases with an older schema version

All installations are past schema version 2, so the one-time reset is gone;
data is never deleted on start (demo mode still resets). user_version is kept
for future migrations. Legal test uses an in-memory DB (cleanup race).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 06:43:51 +01:00
1 parent 1df32ece92
commit 108d166990
5 files changed
+12 -25

No files matched your search

+1 -1
View File
@@ -80,7 +80,7 @@ teams 1─n team_members n─1 users
- `users.username` wird aus dem E-Mail-Präfix aufgefüllt. - `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. - 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. - Standardwerte in `system_settings` (`INSERT OR IGNORE`): `sharing_mode=organizations`, `allow_registration=false`, `instance_name=MindSift`, SMTP leer.
- **Schema-Version:** `PRAGMA user_version` hält `schemaVersion` (derzeit 2). Ist die Datenbank älter, werden vor allem anderen **alle Tabellen gelöscht** und neu angelegt. Bewusst so, solange MindSift nicht produktiv läuft. - **Schema-Version:** `PRAGMA user_version` hält `schemaVersion` (derzeit 2), damit spätere Versionen erkennen, welche Migrationen eine Datenbank schon hat. Daten werden beim Start nie gelöscht (das einmalige Zurücksetzen auf Version 2 ist entfernt, alle Installationen sind darüber hinweg). Nur der Demo-Modus leert die Datenbank bei jedem Start.
## Sicherung ## Sicherung
+1 -1
View File
@@ -115,7 +115,7 @@ Schweregrade: **Hoch** (Datenabfluss oder Rechteumgehung möglich), **Mittel** (
- **Maßnahme:** Fälligkeit mit Uhrzeit speichern, `alarm_sent` bei Änderung von `due_date` zurücksetzen, Wiederholungen begrenzen. - **Maßnahme:** Fälligkeit mit Uhrzeit speichern, `alarm_sent` bei Änderung von `due_date` zurücksetzen, Wiederholungen begrenzen.
### FLAW-13: Migrationen ### FLAW-13: Migrationen
- **Teilweise behoben:** Das Schema hat eine Version (`PRAGMA user_version`, `schemaVersion` in `sqlite.go`). Ist eine Datenbank älter, wird sie beim Start **komplett gelöscht** und neu angelegt (vor dem produktiven Einsatz bewusst so gewählt). Die übrigen Punkte unten gelten weiter. - **Teilweise behoben:** Das Schema hat eine Version (`PRAGMA user_version`, `schemaVersion` in `sqlite.go`), auf die künftige Migrationen aufsetzen können. Die übrigen Punkte unten gelten weiter.
- **Fundstellen:** `Store.migrate` in [`internal/storage/sqlite.go`](../internal/storage/sqlite.go), [`migrations/001_init.sql`](../migrations/001_init.sql). - **Fundstellen:** `Store.migrate` in [`internal/storage/sqlite.go`](../internal/storage/sqlite.go), [`migrations/001_init.sql`](../migrations/001_init.sql).
- **Probleme:** - **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. - 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.
+2 -1
View File
@@ -1369,7 +1369,8 @@ func TestFirstBootSetupWithSampleData(t *testing.T) {
// Impressum and Datenschutz must be reachable without login; unset texts stay hidden. // Impressum and Datenschutz must be reachable without login; unset texts stay hidden.
func TestLegalTextsArePublic(t *testing.T) { func TestLegalTextsArePublic(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "legal.db") // In memory: no files the cleanup could race with
dbPath := ":memory:"
store, err := storage.NewStore(dbPath) store, err := storage.NewStore(dbPath)
if err != nil { if err != nil {
t.Fatalf("NewStore: %v", err) t.Fatalf("NewStore: %v", err)
+3 -18
View File
@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -58,22 +57,11 @@ func (s *Store) Close() error {
return s.db.Close() return s.db.Close()
} }
// schemaVersion is stored in PRAGMA user_version. Databases from an older version are wiped and rebuilt // schemaVersion is stored in PRAGMA user_version so later releases can tell which migrations a database
// on start: MindSift is not in production yet, so breaking changes need no migration path. // already has. (Version 2 once wiped older databases on start; all installations are past that now.)
const schemaVersion = 2 const schemaVersion = 2
func (s *Store) resetOutdatedSchema() error { // dropAllTables removes every table; used by the demo mode reset.
var version int
if err := s.db.QueryRow(`PRAGMA user_version`).Scan(&version); err != nil {
return err
}
if version >= schemaVersion {
return nil
}
slog.Warn("Veraltetes Datenbankschema: alle Daten werden gelöscht und neu angelegt", "version", version, "neu", schemaVersion)
return s.dropAllTables()
}
func (s *Store) dropAllTables() error { func (s *Store) dropAllTables() error {
conn, err := s.db.Conn(context.Background()) conn, err := s.db.Conn(context.Background())
if err != nil { if err != nil {
@@ -107,9 +95,6 @@ func (s *Store) dropAllTables() error {
} }
func (s *Store) migrate() error { func (s *Store) migrate() error {
if err := s.resetOutdatedSchema(); err != nil {
return fmt.Errorf("reset outdated schema: %w", err)
}
schema := ` schema := `
PRAGMA journal_mode = WAL; PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL; PRAGMA synchronous = NORMAL;
+5 -4
View File
@@ -144,8 +144,9 @@ func TestVisibilityFollowsOrganizations(t *testing.T) {
} }
} }
func TestOutdatedSchemaIsWiped(t *testing.T) { // Existing data must survive a restart, whatever user_version the database carries.
path := filepath.Join(t.TempDir(), "old.db") func TestDataSurvivesRestart(t *testing.T) {
path := filepath.Join(t.TempDir(), "keep.db")
store, err := NewStore(path) store, err := NewStore(path)
if err != nil { if err != nil {
t.Fatalf("NewStore failed: %v", err) t.Fatalf("NewStore failed: %v", err)
@@ -159,8 +160,8 @@ func TestOutdatedSchemaIsWiped(t *testing.T) {
t.Fatalf("reopen failed: %v", err) t.Fatalf("reopen failed: %v", err)
} }
defer store.Close() defer store.Close()
if n, _ := store.CountUsers(); n != 0 { if n, _ := store.CountUsers(); n != 1 {
t.Fatalf("outdated database should start empty, found %d users", n) t.Fatalf("data must be kept on restart, found %d users", n)
} }
var v int var v int
_ = store.db.QueryRow(`PRAGMA user_version`).Scan(&v) _ = store.db.QueryRow(`PRAGMA user_version`).Scan(&v)