package storage import ( "encoding/json" "strconv" "strings" "github.com/sandrodach/mindsift/internal/domain" ) // --- STATS, SEARCH & EXPORT --- func (s *Store) GetDashboardStats(userID string) (*domain.DashboardStats, error) { st := &domain.DashboardStats{} _ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE user_id = ? AND status = 'inbox'`, userID).Scan(&st.InboxCount) // Same tasks as GetTasks without a list ("Alle Aufgaben"): own ones plus those in shared task lists taskScope := `(user_id = ? OR task_list_id IN (` + s.accessibleTaskListIDsQuery() + `)) AND (status = 'kept' OR (status = 'converted' AND converted_type = 'task'))` _ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE `+taskScope+` AND is_completed = 0`, userID, userID, userID, userID).Scan(&st.TasksOpenCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE `+taskScope+` AND is_completed = 1`, userID, userID, userID, userID).Scan(&st.TasksDoneCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM documents WHERE user_id = ? AND is_archived = 0`, userID).Scan(&st.DocsCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE user_id = ? AND status = 'discarded'`, userID).Scan(&st.DiscardedCount) // Own lists and lists shared with the user directly or through a group visibleLists := ` SELECT l.id FROM lists l LEFT JOIN list_shares ls ON ls.list_id = l.id AND ( (ls.target_type = 'user' AND ls.target_id = ?) OR (ls.target_type = 'team' AND ls.target_id IN (SELECT team_id FROM team_members WHERE user_id = ?)) ) WHERE l.user_id = ? OR ls.id IS NOT NULL` _ = s.db.QueryRow(`SELECT COUNT(DISTINCT id) FROM (`+visibleLists+`)`, userID, userID, userID).Scan(&st.ListsCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM list_items WHERE is_completed = 0 AND list_id IN (`+visibleLists+`)`, userID, userID, userID).Scan(&st.ListItemsOpenCount) // Count open (uncompleted) To-Dos across accessible workspace documents (owned or shared) docRows, err := s.db.Query(` SELECT blocks_json FROM documents WHERE (user_id = ? OR id IN (SELECT doc_id FROM doc_shares WHERE user_id = ?)) AND is_archived = 0`, userID, userID, ) if err == nil { for docRows.Next() { var bj string if err := docRows.Scan(&bj); err == nil && bj != "" { var blocks []domain.DocumentBlock if err := json.Unmarshal([]byte(bj), &blocks); err == nil { for _, b := range blocks { if b.Type == "todo" && !b.Checked { st.DocsTodosOpenCount++ } } } } } _ = docRows.Close() } return st, nil } func (s *Store) SearchAll(userID, query string) ([]domain.Thought, []domain.Document, error) { res, err := s.SearchGlobal(userID, query) if err != nil { return nil, nil, err } return res.Thoughts, res.Documents, nil } func (s *Store) ExportUserData(userID string) (*domain.FullExport, error) { user, err := s.GetUserByID(userID) if err != nil { return nil, err } thoughts, _ := s.GetThoughts(userID, "all", 5000) docs, _ := s.GetDocuments(userID) lists, _ := s.GetAllListsForUser(userID) taskLists, _ := s.GetTaskLists(userID) themes, _ := s.GetUserThemes(userID) return &domain.FullExport{ ExportedAt: domain.NowISO(), Version: "1.0.0", User: *user, Thoughts: thoughts, Documents: docs, Lists: lists, TaskLists: taskLists, Themes: themes, }, nil } // --- SYSTEM SETTINGS --- func (s *Store) SeedSMTPDefaultsFromEnv(host, port, user, pass, from string, tls, skipVerify bool) { cur, err := s.GetSystemSettings() if err != nil || cur == nil { return } // Only populate DB from env if DB has no SMTP host configured yet and env provides one if cur.SMTPHost == "" && strings.TrimSpace(host) != "" { cur.SMTPHost = strings.TrimSpace(host) if strings.TrimSpace(port) != "" { cur.SMTPPort = strings.TrimSpace(port) } cur.SMTPUser = strings.TrimSpace(user) cur.SMTPPass = pass cur.SMTPFrom = strings.TrimSpace(from) cur.SMTPTLS = tls cur.SMTPSkipVerify = skipVerify _ = s.UpdateSystemSettings(cur) } } func (s *Store) GetSystemSettings() (*domain.SystemSettings, error) { settings := &domain.SystemSettings{ SharingMode: SharingOrganizations, AllowRegistration: false, InstanceName: "MindSift", SMTPPort: "587", SMTPTLS: true, BackupTime: DefaultBackupTime, BackupRetentionDays: DefaultBackupRetentionDays, } rows, err := s.db.Query(`SELECT key, value FROM system_settings`) if err != nil { return settings, nil } defer rows.Close() for rows.Next() { var k, v string if err := rows.Scan(&k, &v); err == nil { switch k { case "sharing_mode": settings.SharingMode = v case "allow_registration": settings.AllowRegistration = (v == "true" || v == "1") case "instance_name": settings.InstanceName = v case "smtp_host": settings.SMTPHost = v case "smtp_port": if v != "" { settings.SMTPPort = v } case "smtp_user": settings.SMTPUser = v case "smtp_pass": settings.SMTPPass = v case "smtp_from": settings.SMTPFrom = v case "smtp_tls": settings.SMTPTLS = (v != "false" && v != "0") case "smtp_skip_verify": settings.SMTPSkipVerify = v == "true" case "allow_private_webhooks": settings.AllowPrivateWebhooks = v == "true" case "backup_enabled": settings.BackupEnabled = v == "true" case "backup_time": if validBackupTime(v) { settings.BackupTime = v } case "backup_retention_days": if n, err := strconv.Atoi(v); err == nil && n > 0 { settings.BackupRetentionDays = n } } } } return settings, nil } func (s *Store) UpdateSystemSettings(settings *domain.SystemSettings) error { regStr := "false" if settings.AllowRegistration { regStr = "true" } tlsStr := "false" if settings.SMTPTLS { tlsStr = "true" } skipVerifyStr := "false" if settings.SMTPSkipVerify { skipVerifyStr = "true" } privateHooksStr := "false" if settings.AllowPrivateWebhooks { privateHooksStr = "true" } if settings.SMTPPort == "" { settings.SMTPPort = "587" } settings.SharingMode = NormalizeSharingMode(settings.SharingMode) if settings.InstanceName == "" { settings.InstanceName = "MindSift" } if !validBackupTime(settings.BackupTime) { settings.BackupTime = DefaultBackupTime } if settings.BackupRetentionDays < 1 || settings.BackupRetentionDays > 365 { settings.BackupRetentionDays = DefaultBackupRetentionDays } _, err := s.db.Exec(`INSERT INTO system_settings (key, value) VALUES ('sharing_mode', ?), ('allow_registration', ?), ('instance_name', ?), ('smtp_host', ?), ('smtp_port', ?), ('smtp_user', ?), ('smtp_pass', ?), ('smtp_from', ?), ('smtp_tls', ?), ('smtp_skip_verify', ?), ('allow_private_webhooks', ?), ('backup_enabled', ?), ('backup_time', ?), ('backup_retention_days', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`, settings.SharingMode, regStr, settings.InstanceName, strings.TrimSpace(settings.SMTPHost), strings.TrimSpace(settings.SMTPPort), strings.TrimSpace(settings.SMTPUser), settings.SMTPPass, strings.TrimSpace(settings.SMTPFrom), tlsStr, skipVerifyStr, privateHooksStr, strconv.FormatBool(settings.BackupEnabled), settings.BackupTime, strconv.Itoa(settings.BackupRetentionDays), ) return err }