package config import ( "encoding/json" "os" "sync" ) // Store persists web-UI editable runtime state (sources added/edited) to a // JSON file so edits survive restarts. Base YAML sources are merged underneath. type Store struct { mu sync.Mutex path string data RuntimeConfig } func NewStore(path string) *Store { return &Store{path: path} } // Load reads the runtime file (missing file = empty state). func (s *Store) Load() error { s.mu.Lock() defer s.mu.Unlock() data, err := os.ReadFile(s.path) if err != nil { if os.IsNotExist(err) { s.data = RuntimeConfig{} return nil } return err } return json.Unmarshal(data, &s.data) } // List returns the runtime sources (those edited via web UI). func (s *Store) List() []Source { s.mu.Lock() defer s.mu.Unlock() out := make([]Source, len(s.data.Sources)) copy(out, s.data.Sources) return out } // Upsert adds or replaces a runtime source and persists. func (s *Store) Upsert(src Source) error { s.mu.Lock() defer s.mu.Unlock() for i := range s.data.Sources { if s.data.Sources[i].Name == src.Name { s.data.Sources[i] = src return s.persistLocked() } } s.data.Sources = append(s.data.Sources, src) return s.persistLocked() } // Remove deletes a runtime source and persists. func (s *Store) Remove(name string) (bool, error) { s.mu.Lock() defer s.mu.Unlock() kept := s.data.Sources[:0] removed := false for _, src := range s.data.Sources { if src.Name == name { removed = true continue } kept = append(kept, src) } if !removed { return false, nil } s.data.Sources = kept return true, s.persistLocked() } func (s *Store) persistLocked() error { b, err := json.MarshalIndent(s.data, "", " ") if err != nil { return err } return os.WriteFile(s.path, b, 0644) } // ListKeys returns the persisted gateway keys. func (s *Store) ListKeys() []GWKey { s.mu.Lock() defer s.mu.Unlock() out := make([]GWKey, len(s.data.Keys)) copy(out, s.data.Keys) return out } // KeyByValue looks up a gateway key record by its secret value. func (s *Store) KeyByValue(key string) (GWKey, bool) { s.mu.Lock() defer s.mu.Unlock() for _, k := range s.data.Keys { if k.Key == key { return k, true } } return GWKey{}, false } // SaveKey upserts a gateway key record and persists. func (s *Store) SaveKey(k GWKey) error { s.mu.Lock() defer s.mu.Unlock() for i := range s.data.Keys { if s.data.Keys[i].Key == k.Key { s.data.Keys[i] = k return s.persistLocked() } } s.data.Keys = append(s.data.Keys, k) return s.persistLocked() } // DeleteKey removes a gateway key record and persists. func (s *Store) DeleteKey(key string) (bool, error) { s.mu.Lock() defer s.mu.Unlock() kept := s.data.Keys[:0] removed := false for _, k := range s.data.Keys { if k.Key == key { removed = true continue } kept = append(kept, k) } if !removed { return false, nil } s.data.Keys = kept return true, s.persistLocked() }