diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-31 20:16:12 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-31 20:16:12 -1000 |
| commit | cbb0b53de1d06918c142171fd084f14f03798bc1 (patch) | |
| tree | beb642057178bce8f50e3ad67f5a62671e3e6dda /internal/store | |
| parent | d39220eac03fbc5b714bde989665ed1c92dd24a5 (diff) | |
Add feature toggles system with settings UI (#74)
- Add feature_toggles table (migration 012)
- Add source_config table for future source selection (migration 013)
- Create settings page at /settings with:
- Feature toggle management (enable/disable/create/delete)
- Data source configuration (sync and toggle boards/calendars)
- Add store methods for feature toggles and source config
- Add GetCalendarList and GetTaskLists to Google API clients
- Document feature toggle workflow in DESIGN.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/store')
| -rw-r--r-- | internal/store/sqlite.go | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index 48bcae5..465c3c1 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -1193,3 +1193,182 @@ func (s *Store) GetCompletedTasks(limit int) ([]models.CompletedTask, error) { } return tasks, rows.Err() } + +// Source configuration + +// GetSourceConfigs retrieves all source configurations +func (s *Store) GetSourceConfigs() ([]models.SourceConfig, error) { + rows, err := s.db.Query(` + SELECT id, source, item_type, item_id, item_name, enabled + FROM source_config + ORDER BY source, item_type, item_name + `) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var configs []models.SourceConfig + for rows.Next() { + var cfg models.SourceConfig + if err := rows.Scan(&cfg.ID, &cfg.Source, &cfg.ItemType, &cfg.ItemID, &cfg.ItemName, &cfg.Enabled); err != nil { + return nil, err + } + configs = append(configs, cfg) + } + return configs, rows.Err() +} + +// GetSourceConfigsBySource retrieves configurations for a specific source +func (s *Store) GetSourceConfigsBySource(source string) ([]models.SourceConfig, error) { + rows, err := s.db.Query(` + SELECT id, source, item_type, item_id, item_name, enabled + FROM source_config + WHERE source = ? + ORDER BY item_type, item_name + `, source) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var configs []models.SourceConfig + for rows.Next() { + var cfg models.SourceConfig + if err := rows.Scan(&cfg.ID, &cfg.Source, &cfg.ItemType, &cfg.ItemID, &cfg.ItemName, &cfg.Enabled); err != nil { + return nil, err + } + configs = append(configs, cfg) + } + return configs, rows.Err() +} + +// GetEnabledSourceIDs returns enabled item IDs for a source and type +func (s *Store) GetEnabledSourceIDs(source, itemType string) ([]string, error) { + rows, err := s.db.Query(` + SELECT item_id FROM source_config + WHERE source = ? AND item_type = ? AND enabled = 1 + `, source, itemType) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var ids []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + ids = append(ids, id) + } + return ids, rows.Err() +} + +// UpsertSourceConfig creates or updates a source configuration +func (s *Store) UpsertSourceConfig(cfg models.SourceConfig) error { + _, err := s.db.Exec(` + INSERT INTO source_config (source, item_type, item_id, item_name, enabled, updated_at) + VALUES (?, ?, ?, ?, ?, datetime('now', 'localtime')) + ON CONFLICT(source, item_type, item_id) DO UPDATE SET + item_name = excluded.item_name, + enabled = excluded.enabled, + updated_at = datetime('now', 'localtime') + `, cfg.Source, cfg.ItemType, cfg.ItemID, cfg.ItemName, cfg.Enabled) + return err +} + +// SetSourceConfigEnabled updates the enabled state for a config item +func (s *Store) SetSourceConfigEnabled(source, itemType, itemID string, enabled bool) error { + _, err := s.db.Exec(` + UPDATE source_config SET enabled = ?, updated_at = datetime('now', 'localtime') + WHERE source = ? AND item_type = ? AND item_id = ? + `, enabled, source, itemType, itemID) + return err +} + +// SyncSourceConfigs updates the config table with available items from a source +func (s *Store) SyncSourceConfigs(source, itemType string, items []models.SourceConfig) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + for _, item := range items { + // Insert new items as enabled by default, preserve existing enabled state + _, err := tx.Exec(` + INSERT INTO source_config (source, item_type, item_id, item_name, enabled, updated_at) + VALUES (?, ?, ?, ?, 1, datetime('now', 'localtime')) + ON CONFLICT(source, item_type, item_id) DO UPDATE SET + item_name = excluded.item_name, + updated_at = datetime('now', 'localtime') + `, source, itemType, item.ItemID, item.ItemName) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// Feature toggles + +// GetFeatureToggles returns all feature toggles +func (s *Store) GetFeatureToggles() ([]models.FeatureToggle, error) { + rows, err := s.db.Query(` + SELECT id, name, description, enabled FROM feature_toggles ORDER BY name + `) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var toggles []models.FeatureToggle + for rows.Next() { + var t models.FeatureToggle + var desc sql.NullString + if err := rows.Scan(&t.ID, &t.Name, &desc, &t.Enabled); err != nil { + return nil, err + } + if desc.Valid { + t.Description = desc.String + } + toggles = append(toggles, t) + } + return toggles, rows.Err() +} + +// IsFeatureEnabled checks if a feature toggle is enabled +func (s *Store) IsFeatureEnabled(name string) bool { + var enabled bool + err := s.db.QueryRow(`SELECT enabled FROM feature_toggles WHERE name = ?`, name).Scan(&enabled) + if err != nil { + return false + } + return enabled +} + +// SetFeatureEnabled updates a feature toggle's enabled state +func (s *Store) SetFeatureEnabled(name string, enabled bool) error { + _, err := s.db.Exec(` + UPDATE feature_toggles SET enabled = ?, updated_at = datetime('now', 'localtime') + WHERE name = ? + `, enabled, name) + return err +} + +// CreateFeatureToggle creates a new feature toggle +func (s *Store) CreateFeatureToggle(name, description string, enabled bool) error { + _, err := s.db.Exec(` + INSERT INTO feature_toggles (name, description, enabled) + VALUES (?, ?, ?) + `, name, description, enabled) + return err +} + +// DeleteFeatureToggle removes a feature toggle +func (s *Store) DeleteFeatureToggle(name string) error { + _, err := s.db.Exec(`DELETE FROM feature_toggles WHERE name = ?`, name) + return err +} |
