MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
Pure moves, no behaviour change: the storage layer now has users, apikeys, thoughts, tasklists, documents, folders, tags, lists, teams and system files next to sqlite.go (store, schema, migrations); the API has handlers_*.go per area plus realtime.go and alarms.go next to server.go (server, router, middleware). Declarations were moved verbatim with an AST-based tool; imports pruned with goimports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
209 lines
6.0 KiB
Go
209 lines
6.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"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)
|
|
_ = s.db.QueryRow(`
|
|
SELECT COUNT(DISTINCT 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`,
|
|
userID, userID, userID).Scan(&st.ListsCount)
|
|
|
|
// 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)
|
|
|
|
return &domain.FullExport{
|
|
ExportedAt: domain.NowISO(),
|
|
Version: "1.0.0",
|
|
User: *user,
|
|
Thoughts: thoughts,
|
|
Documents: docs,
|
|
Lists: lists,
|
|
TaskLists: taskLists,
|
|
}, 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,
|
|
}
|
|
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
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"
|
|
}
|
|
_, 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', ?)
|
|
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,
|
|
)
|
|
return err
|
|
}
|