| |
| package persistence |
|
|
| import ( |
| "context" |
| "os" |
| "path/filepath" |
| "sync" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/domain/errors" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/domain/ports" |
| ) |
|
|
| |
| |
| type ConfigRepository struct { |
| configPath string |
| mu sync.RWMutex |
| } |
|
|
| |
| func NewConfigRepository(configPath string) *ConfigRepository { |
| return &ConfigRepository{ |
| configPath: configPath, |
| } |
| } |
|
|
| |
| func (r *ConfigRepository) Load(ctx context.Context) (*config.Config, error) { |
| r.mu.RLock() |
| defer r.mu.RUnlock() |
|
|
| if r.configPath == "" { |
| return nil, errors.ErrConfigNotFound |
| } |
|
|
| cfg, err := config.LoadConfig(r.configPath) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return nil, errors.Wrap(errors.NotFound, "configuration file not found", err) |
| } |
| return nil, errors.Wrap(errors.InternalError, "failed to load configuration", err) |
| } |
|
|
| return cfg, nil |
| } |
|
|
| |
| func (r *ConfigRepository) Save(ctx context.Context, cfg *config.Config) error { |
| return r.SaveWithPath(ctx, r.configPath, cfg) |
| } |
|
|
| |
| func (r *ConfigRepository) SaveWithPath(ctx context.Context, path string, cfg *config.Config) error { |
| r.mu.Lock() |
| defer r.mu.Unlock() |
|
|
| if path == "" { |
| return errors.New(errors.InvalidInput, "config path is empty") |
| } |
|
|
| if cfg == nil { |
| return errors.New(errors.InvalidInput, "config is nil") |
| } |
|
|
| |
| if err := r.Validate(ctx, cfg); err != nil { |
| return err |
| } |
|
|
| |
| dir := filepath.Dir(path) |
| if err := os.MkdirAll(dir, 0755); err != nil { |
| return errors.Wrap(errors.InternalError, "failed to create config directory", err) |
| } |
|
|
| |
| if err := config.SaveConfigPreserveComments(path, cfg); err != nil { |
| return errors.Wrap(errors.InternalError, "failed to save configuration", err) |
| } |
|
|
| return nil |
| } |
|
|
| |
| func (r *ConfigRepository) Validate(ctx context.Context, cfg *config.Config) error { |
| if cfg == nil { |
| return errors.ErrInvalidConfig.WithDetail("reason", "configuration is nil") |
| } |
|
|
| |
| tmpDir := filepath.Dir(r.configPath) |
| if tmpDir == "" { |
| tmpDir = os.TempDir() |
| } |
|
|
| tmpFile, err := os.CreateTemp(tmpDir, "config-validate-*.yaml") |
| if err != nil { |
| return errors.Wrap(errors.InternalError, "failed to create temp file for validation", err) |
| } |
| tmpPath := tmpFile.Name() |
|
|
| |
| _ = tmpFile.Close() |
| defer os.Remove(tmpPath) |
|
|
| |
| _, err = config.LoadConfigOptional(tmpPath, false) |
| if err != nil { |
| return errors.Wrap(errors.ValidationFailed, "configuration validation failed", err) |
| } |
|
|
| return nil |
| } |
|
|
| |
| func (r *ConfigRepository) GetConfigPath() string { |
| r.mu.RLock() |
| defer r.mu.RUnlock() |
| return r.configPath |
| } |
|
|
| |
| func (r *ConfigRepository) SetConfigPath(path string) { |
| r.mu.Lock() |
| defer r.mu.Unlock() |
| r.configPath = path |
| } |
|
|
| |
| var _ ports.ConfigRepository = (*ConfigRepository)(nil) |
|
|