From 0fd54eddc40f517cf491310d4f8a60b0d79dc937 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 4 Mar 2026 11:12:44 -1000 Subject: feat: sync log, cache clear endpoint, Todoist projects from cached tasks - migration 016: sync_log table - store: AddSyncLogEntry, GetRecentSyncLog, InvalidateAllCaches, GetProjectsFromTasks - settings: HandleClearCache (POST /settings/clear-cache), SyncLog in page data - settings: use GetProjectsFromTasks instead of deprecated Todoist REST /projects - handlers: populate atom projects from store - agent: log warning on registration failure instead of silently swallowing - google_tasks: simplify URL literal - tests: sync log CRUD, clear cache handler, settings page includes sync log, sync sources adds log entry, incremental sync paths, task completion response/headers, calendar cache fallback Co-Authored-By: Claude Sonnet 4.6 --- internal/store/sqlite.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'internal/store/sqlite.go') diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index 158febc..366b24e 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -1397,6 +1397,37 @@ func (s *Store) SyncSourceConfigs(source, itemType string, items []models.Source return tx.Commit() } +// InvalidateAllCaches removes cache metadata for all known cache keys +func (s *Store) InvalidateAllCaches() error { + _, err := s.db.Exec(`DELETE FROM cache_metadata WHERE key IN (?, ?, ?, ?)`, + CacheKeyTodoistTasks, CacheKeyTrelloBoards, CacheKeyPlanToEatMeals, CacheKeyGoogleCalendar) + return err +} + +// GetProjectsFromTasks returns distinct projects from the tasks table +func (s *Store) GetProjectsFromTasks() ([]models.Project, error) { + rows, err := s.db.Query(` + SELECT DISTINCT project_id, project_name + FROM tasks + WHERE project_id != '' + ORDER BY project_name + `) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var projects []models.Project + for rows.Next() { + var p models.Project + if err := rows.Scan(&p.ID, &p.Name); err != nil { + return nil, err + } + projects = append(projects, p) + } + return projects, rows.Err() +} + // Feature toggles // GetFeatureToggles returns all feature toggles @@ -1457,3 +1488,42 @@ func (s *Store) DeleteFeatureToggle(name string) error { _, err := s.db.Exec(`DELETE FROM feature_toggles WHERE name = ?`, name) return err } + +// SyncLogEntry represents a single entry in the sync activity log +type SyncLogEntry struct { + ID int64 + EventType string + Message string + CreatedAt time.Time +} + +// AddSyncLogEntry records a sync or cache event in the log +func (s *Store) AddSyncLogEntry(eventType, message string) error { + _, err := s.db.Exec( + `INSERT INTO sync_log (event_type, message) VALUES (?, ?)`, + eventType, message, + ) + return err +} + +// GetRecentSyncLog returns the most recent sync log entries, newest first +func (s *Store) GetRecentSyncLog(limit int) ([]SyncLogEntry, error) { + rows, err := s.db.Query( + `SELECT id, event_type, message, created_at FROM sync_log ORDER BY id DESC LIMIT ?`, + limit, + ) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var entries []SyncLogEntry + for rows.Next() { + var e SyncLogEntry + if err := rows.Scan(&e.ID, &e.EventType, &e.Message, &e.CreatedAt); err != nil { + return nil, err + } + entries = append(entries, e) + } + return entries, rows.Err() +} -- cgit v1.2.3