diff --git a/cmd/server/main.go b/cmd/server/main.go index 9333166..e04bf9d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -37,18 +37,18 @@ func main() { } defer store.Close() - // Seed SMTP defaults from environment variables into DB if not yet configured in DB - store.SeedSMTPDefaultsFromEnv(cfg.SMTPHost, cfg.SMTPPort, cfg.SMTPUser, cfg.SMTPPass, cfg.SMTPFrom, cfg.SMTPTLS, cfg.SMTPSkipVerify) - if cfg.DemoMode { - slog.Info("🧪 Demo-Modus aktiv (MINDA_DEMO=true): Richte Demo-Benutzer admin/admin und user/user ein...") - if err := store.EnsureDemoAccounts(); err != nil { - slog.Warn("⚠️ Fehler beim Anlegen der Demo-Accounts", "error", err) + slog.Info("🧪 Demo-Modus aktiv: Setze Datenbank zurück und richte Demo-Accounts ein...") + if err := store.ResetAndSeedDemo(); err != nil { + slog.Warn("⚠️ Fehler beim Einrichten des Demo-Modus", "error", err) } else { slog.Info("✅ Demo-Accounts bereit: admin / admin (Admin-Rolle) und user / user (Standard-Rolle)") } } + // Seed SMTP defaults from environment variables into DB if not yet configured in DB + store.SeedSMTPDefaultsFromEnv(cfg.SMTPHost, cfg.SMTPPort, cfg.SMTPUser, cfg.SMTPPass, cfg.SMTPFrom, cfg.SMTPTLS, cfg.SMTPSkipVerify) + wsHub := ws.NewHub() staticFS, err := fs.Sub(embeddedDist, "dist") diff --git a/internal/storage/demo.go b/internal/storage/demo.go new file mode 100644 index 0000000..bee0bcf --- /dev/null +++ b/internal/storage/demo.go @@ -0,0 +1,21 @@ +package storage + +import ( + "fmt" + "log/slog" +) + +// ResetAndSeedDemo drops all tables, recreates the schema, and seeds demo accounts and demo data. +func (s *Store) ResetAndSeedDemo() error { + if err := s.dropAllTables(); err != nil { + return fmt.Errorf("reset demo: drop all tables: %w", err) + } + if err := s.migrate(); err != nil { + return fmt.Errorf("reset demo: migrate schema: %w", err) + } + if err := s.EnsureDemoAccounts(); err != nil { + return fmt.Errorf("reset demo: ensure demo accounts: %w", err) + } + slog.Info("🧹 Demo-Modus: Datenbank für Testumgebung zurückgesetzt und neu befüllt") + return nil +} diff --git a/internal/storage/demo_test.go b/internal/storage/demo_test.go new file mode 100644 index 0000000..21f637c --- /dev/null +++ b/internal/storage/demo_test.go @@ -0,0 +1,104 @@ +package storage + +import ( + "path/filepath" + "testing" + + "github.com/sandrodach/mindsift/internal/auth" +) + +func TestResetAndSeedDemo(t *testing.T) { + tmpDir := t.TempDir() + dbPath := filepath.Join(tmpDir, "demo_test.db") + + store, err := NewStore(dbPath) + if err != nil { + t.Fatalf("NewStore failed: %v", err) + } + defer store.Close() + + // Create custom user and data + hash, _ := auth.HashPassword("secret") + customUser, err := store.CreateUserWithUsername("custom@test.local", "customuser", hash, "Custom", "member") + if err != nil { + t.Fatalf("CreateUser failed: %v", err) + } + _, err = store.CreateThought(customUser.ID, "A personal secret note", "normal", "manual", "") + if err != nil { + t.Fatalf("CreateThought failed: %v", err) + } + + // Call ResetAndSeedDemo + if err := store.ResetAndSeedDemo(); err != nil { + t.Fatalf("ResetAndSeedDemo failed: %v", err) + } + + // Custom user must be wiped + u, err := store.GetUserByID(customUser.ID) + if err == nil && u != nil { + t.Fatalf("expected custom user to be wiped, but still found: %v", u) + } + + // Demo accounts admin and user must exist + admin, err := store.GetUserByLogin("admin") + if err != nil || admin == nil { + t.Fatalf("expected demo admin to exist after reset: %v", err) + } + user, err := store.GetUserByLogin("user") + if err != nil || user == nil { + t.Fatalf("expected demo user to exist after reset: %v", err) + } + + // Resetting again should work cleanly (idempotence on repeated server starts) + if err := store.ResetAndSeedDemo(); err != nil { + t.Fatalf("second ResetAndSeedDemo failed: %v", err) + } +} + +func TestWithoutDemoModeDataIsNotWiped(t *testing.T) { + tmpDir := t.TempDir() + dbPath := filepath.Join(tmpDir, "nodemo_test.db") + + // 1. Initial start without demo mode + store1, err := NewStore(dbPath) + if err != nil { + t.Fatalf("first NewStore failed: %v", err) + } + + hash, _ := auth.HashPassword("secret123") + user1, err := store1.CreateUserWithUsername("persistent@example.com", "persistent", hash, "Persistent", "member") + if err != nil { + t.Fatalf("CreateUserWithUsername failed: %v", err) + } + thought1, err := store1.CreateThought(user1.ID, "Important persistent note #keep", "high", "manual", "") + if err != nil { + t.Fatalf("CreateThought failed: %v", err) + } + + // Close store simulating server shutdown + store1.Close() + + // 2. Server restart without demo mode (standard NewStore) + store2, err := NewStore(dbPath) + if err != nil { + t.Fatalf("second NewStore failed: %v", err) + } + defer store2.Close() + + // Verify existing data is preserved + foundUser, err := store2.GetUserByID(user1.ID) + if err != nil || foundUser == nil { + t.Fatalf("expected user %s to be preserved without demo mode, err: %v", user1.ID, err) + } + if foundUser.Username != "persistent" { + t.Fatalf("expected username 'persistent', got '%s'", foundUser.Username) + } + + thoughts, err := store2.GetThoughts(user1.ID, "", 10) + if err != nil { + t.Fatalf("GetThoughts failed: %v", err) + } + if len(thoughts) != 1 || thoughts[0].ID != thought1.ID { + t.Fatalf("expected thought %s to persist, got: %v", thought1.ID, thoughts) + } +} diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index d3b68fe..8cdfef9 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -70,6 +70,11 @@ func (s *Store) resetOutdatedSchema() error { 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 { conn, err := s.db.Conn(context.Background()) if err != nil { return err @@ -88,9 +93,7 @@ func (s *Store) resetOutdatedSchema() error { } } _ = rows.Close() - if len(tables) > 0 { - slog.Warn("Veraltetes Datenbankschema: alle Daten werden gelöscht und neu angelegt", "version", version, "neu", schemaVersion) - } + if _, err := conn.ExecContext(ctx, `PRAGMA foreign_keys = OFF`); err != nil { return err }