package storage import ( "time" "github.com/sandrodach/mindsift/internal/domain" ) // seedBlock describes one outliner bullet of the sample notes: nesting level, block type and text. type seedBlock struct { level int typ string text string checked bool lang string } func seedBlocks(spec []seedBlock) []domain.DocumentBlock { out := make([]domain.DocumentBlock, len(spec)) for i, b := range spec { typ := b.typ if typ == "" { typ = "bullet" } out[i] = domain.DocumentBlock{ID: domain.NewID("blk"), Type: typ, Content: b.text, Checked: b.checked, Lang: b.lang, Level: b.level} } return out } type seedTask struct { content string priority string due int // days from today; 0 = no due date unless dueToday dueToday bool status string // kanban column notes string subtasks []string doneSubs int } // SeedStarterContent fills a new account with realistic sample data (optional at setup, always in demo mode): // inbox thoughts, two task lists with Kanban states, shopping and packing lists and a few linked notes. func (s *Store) SeedStarterContent(user *domain.User) error { day := func(offset int) string { return time.Now().AddDate(0, 0, offset).Format("2006-01-02") } inbox := []struct{ content, priority, source string }{ {"Zündkerzen und Luftfilter für den Rasenmäher bestellen #garage #wartung", "normal", "manual"}, {"Urlaubsidee: Wanderhütte in Südtirol für Oktober buchen #urlaub #familie", "high", "voice"}, {"Kellerfeuchtigkeit über 68 % gestiegen – Entfeuchter prüfen #smarthome #alarm", "urgent", "webhook"}, {"Buchtipp von Jonas: „Die Kunst des klaren Denkens“ #lesen", "low", "manual"}, {"Geburtstagsgeschenk für Oma – vielleicht ein Fotobuch vom Sommer? #familie", "normal", "manual"}, {"Idee: Backup-Report jeden Montag per Discord schicken #homelab", "normal", "voice"}, } for _, t := range inbox { _, _ = s.CreateThought(user.ID, t.content, t.priority, t.source, "") } addTasks := func(listID string, tasks []seedTask) { for _, t := range tasks { due := "" if t.due != 0 || t.dueToday { due = day(t.due) } th, err := s.CreateThoughtWithDetails(user.ID, t.content, t.priority, "manual", due, listID, t.status, "") if err != nil { continue } if _, err := s.UpdateThoughtTriage(user.ID, th.ID, "keep", "task", "", due, listID); err != nil { continue } subs := make([]domain.SubTask, len(t.subtasks)) for i, title := range t.subtasks { subs[i] = domain.SubTask{ID: domain.NewID("sub"), Title: title, IsCompleted: i < t.doneSubs, CreatedAt: domain.NowISO()} } done := t.status == "done" status, notes := t.status, t.notes _, _ = s.UpdateThoughtDetails(user.ID, th.ID, nil, &done, nil, nil, nil, &status, ¬es, &subs, nil) } } homelab, err := s.CreateTaskList(user.ID, "", "", "Homelab", "🖥️", "#2563eb") if err == nil { addTasks(homelab.ID, []seedTask{ {content: "MindSift-Container auf den neuen Server umziehen #docker", priority: "high", due: 2, status: "in_progress", notes: "Volume `./data` vorher mit `sqlite3 .backup` sichern.", subtasks: []string{"Backup der Datenbank ziehen", "docker-compose.yml anpassen", "DNS auf neue IP umstellen", "Alten Container stoppen"}, doneSubs: 2}, {content: "Reverse-Proxy: Nginx-Timeouts für Long-Polling erhöhen #nginx", priority: "normal", due: 5, status: "todo"}, {content: "USV-Batterie tauschen lassen #hardware", priority: "urgent", due: -1, status: "todo", notes: "Händler hat das Ersatzteil seit Montag da."}, {content: "Wartet auf Lieferung: zweite SSD für das RAID #hardware", priority: "normal", due: 7, status: "waiting"}, {content: "Gitea-Runner auf Version 0.2 aktualisieren #devops", priority: "low", status: "done"}, {content: "Uptime-Monitoring für alle Dienste einrichten #monitoring", priority: "normal", due: 10, status: "todo", subtasks: []string{"Uptime Kuma installieren", "Benachrichtigung per Discord", "Statusseite veröffentlichen"}, doneSubs: 1}, }) } house, err := s.CreateTaskList(user.ID, "", "", "Haus & Garten", "🏡", "#16a34a") if err == nil { addTasks(house.ID, []seedTask{ {content: "Hecke schneiden, bevor die Vögel brüten #garten", priority: "normal", due: 3, status: "todo"}, {content: "Heizungswartung beim Installateur anfragen #wartung", priority: "high", dueToday: true, status: "in_progress"}, {content: "Steuerunterlagen für 2026 sortieren #finanzen", priority: "normal", due: 14, status: "todo", subtasks: []string{"Handwerkerrechnungen", "Spendenquittungen", "Fahrtkosten"}}, {content: "Fahrradschloss nachkaufen", priority: "low", status: "done"}, }) } shop, err := s.CreateList(user.ID, "Wocheneinkauf", "🛒", "shopping") if err == nil { items := []struct{ content, category, qty string }{ {"Hafermilch (Barista Edition)", "Kühlregal", "3x"}, {"Griechischer Joghurt", "Kühlregal", "500 g"}, {"Bergkäse", "Kühlregal", "200 g"}, {"Äpfel (Elstar)", "Obst & Gemüse", "1 kg"}, {"Cherrytomaten", "Obst & Gemüse", "2 Schalen"}, {"Avocados", "Obst & Gemüse", "3x"}, {"Bauernbrot", "Backwaren", "1x"}, {"Espressobohnen (100 % Arabica)", "Kaffee & Tee", "1 kg"}, {"Pfefferminztee", "Kaffee & Tee", "1x"}, {"Spülmaschinentabs", "Drogerie", "1 Pack"}, } for i, it := range items { item, err := s.CreateListItem(shop.ID, it.content, it.category, it.qty, i) if err == nil && (i == 1 || i == 6 || i == 8) { _, _ = s.ToggleListItem(item.ID, true, user.ID, user.DisplayName) } } } packing, err := s.CreateList(user.ID, "Packliste Südtirol", "🎒", "shopping") if err == nil { for i, it := range []struct{ content, category string }{ {"Wanderschuhe", "Kleidung"}, {"Regenjacke", "Kleidung"}, {"Fleecepullover", "Kleidung"}, {"Erste-Hilfe-Set", "Ausrüstung"}, {"Stirnlampe", "Ausrüstung"}, {"Trinkflaschen", "Ausrüstung"}, {"Personalausweise", "Dokumente"}, {"Hüttenschlafsack", "Ausrüstung"}, } { _, _ = s.CreateListItem(packing.ID, it.content, it.category, "", i) } } _, _ = s.CreateFolder(user.ID, "Projekte") _, _ = s.CreateFolder(user.ID, "Privat") _, _ = s.CreateDocument(user.ID, "", "Homelab 2.0 – Umzug auf den neuen Server", "🚀", "Projekte", seedBlocks([]seedBlock{ {typ: "callout", text: "Ziel: Bis Ende des Monats laufen alle Dienste auf dem neuen Mini-PC – leiser, sparsamer und mit echtem Backup. Aufgaben dazu stehen in der Liste #homelab."}, {typ: "h2", text: "Warum überhaupt?"}, {level: 1, text: "Der alte Server zieht 65 W im Leerlauf, der neue nur 9 W"}, {level: 2, text: "Ersparnis ca. 120 € im Jahr bei aktuellem Strompreis"}, {level: 1, text: "Backups liefen bisher nur manuell – das soll sich ändern #backup"}, {level: 1, text: "Endlich Platz für die Dienste, die seit Monaten auf der Wunschliste stehen"}, {typ: "h2", text: "Checkliste Umzug"}, {level: 1, typ: "todo", text: "Vorbereitung"}, {level: 2, typ: "todo", text: "Proxmox installieren und Updates einspielen", checked: true}, {level: 2, typ: "todo", text: "Netzwerk: feste IP und VLAN für Dienste", checked: true}, {level: 2, typ: "todo", text: "ZFS-Pool mit zwei SSDs anlegen"}, {level: 1, typ: "todo", text: "Dienste umziehen"}, {level: 2, typ: "todo", text: "MindSift (Daten liegen komplett in einer SQLite-Datei)", checked: true}, {level: 2, typ: "todo", text: "Gitea mit Runner und Container-Registry"}, {level: 2, typ: "todo", text: "Home Assistant inklusive Zigbee-Stick"}, {level: 1, typ: "todo", text: "Alten Server sicher löschen und verkaufen"}, {typ: "h2", text: "Dienste & Ports"}, {level: 1, text: "**MindSift** – Notizen, Aufgaben und Listen für die ganze Familie #mindsift"}, {level: 2, text: "läuft hinter Nginx, WebSocket-Upgrade und Long-Polling sind freigegeben"}, {level: 2, text: "Demo-Konten nur im Testsystem, nie öffentlich"}, {level: 1, text: "**Gitea** – Code, CI und Images, z. B. `git.example.com` #devops"}, {level: 1, text: "**Home Assistant** – Sensoren melden Alarme per Webhook direkt in die MindSift-Inbox #smarthome"}, {level: 2, text: "Beispiel: Kellerfeuchtigkeit über 65 % → Gedanke mit Priorität „dringend“"}, {typ: "h3", text: "Compose-Ausschnitt"}, {level: 1, typ: "code", lang: "yaml", text: "services:\n mindsift:\n image: git.example.com/sandrodach/mindsift:v0.2\n restart: unless-stopped\n volumes: [\"./data:/data\"]\n environment:\n - JWT_SECRET=${JWT_SECRET}"}, {typ: "h2", text: "Offene Fragen"}, {level: 1, text: "Reicht eine USV für Router und Server zusammen?"}, {level: 1, text: "Offsite-Backup: Hetzner Storage Box oder Festplatte bei den Eltern? Siehe [[Ideen-Sammlung]]"}, }), []string{"homelab", "projekt"}, true) _, _ = s.CreateDocument(user.ID, "", "Urlaub Südtirol", "🏔️", "Privat", seedBlocks([]seedBlock{ {typ: "h2", text: "Reiseplan Oktober"}, {level: 1, text: "Anreise Samstag, früh los wegen Brenner-Stau"}, {level: 1, text: "Hütte bei Villnöß – Bestätigung kommt per Mail"}, {level: 2, text: "Halbpension, Hunde erlaubt"}, {typ: "h2", text: "Touren-Ideen"}, {level: 1, typ: "todo", text: "Adolf-Munkel-Weg unter den Geislerspitzen", checked: true}, {level: 1, typ: "todo", text: "Seceda mit der Seilbahn, oben Höhenweg"}, {level: 1, typ: "todo", text: "Regentag: Ötzi-Museum in Bozen #familie"}, {typ: "callout", text: "Packliste liegt unter Listen → Packliste Südtirol."}, }), []string{"urlaub"}, false) _, _ = s.CreateDocument(user.ID, "", "Ideen-Sammlung", "💡", "", seedBlocks([]seedBlock{ {text: "Offsite-Backup verschlüsselt mit restic #backup"}, {level: 1, text: "Vorteil: Snapshots, Deduplizierung, läuft auf jedem Ziel"}, {text: "Familien-Wiki statt WhatsApp-Gruppe für Rezepte und Termine #familie"}, {text: "Wochenrückblick jeden Sonntag: Inbox leeren, Aufgaben für die Woche planen #routine"}, }), []string{"ideen"}, false) return nil } // seedDemoShares shares part of the admin's sample data with the demo user, so both demo accounts can try // out editing a note, a task list and a shopping list together. func (s *Store) seedDemoShares(admin, member *domain.User) { var docID, taskListID, listID string _ = s.db.QueryRow(`SELECT id FROM documents WHERE user_id = ? AND title LIKE 'Homelab 2.0%'`, admin.ID).Scan(&docID) _ = s.db.QueryRow(`SELECT id FROM task_lists WHERE user_id = ? AND title = 'Homelab'`, admin.ID).Scan(&taskListID) _ = s.db.QueryRow(`SELECT id FROM lists WHERE user_id = ? AND title = 'Wocheneinkauf'`, admin.ID).Scan(&listID) if docID != "" { _, _ = s.CreateDocShare(admin.ID, docID, member.ID, "write") } if taskListID != "" { _, _ = s.AddTaskListShare(taskListID, "user", member.ID, "editor", admin.ID) } if listID != "" { _, _ = s.AddListShare(admin.ID, listID, "user", member.ID, "editor") } }