package config import ( "fmt" "os" "strings" "time" "github.com/spf13/viper" sharedconfig "github.com/geo-platform/tenant-api/internal/shared/config" ) type Config struct { Server sharedconfig.ServerConfig `mapstructure:"server"` Database sharedconfig.DatabaseConfig `mapstructure:"database"` MonitoringDatabase sharedconfig.DatabaseConfig `mapstructure:"monitoring_database"` Redis sharedconfig.RedisConfig `mapstructure:"redis"` Cache sharedconfig.CacheConfig `mapstructure:"cache"` Log sharedconfig.LogConfig `mapstructure:"log"` JWT JWTConfig `mapstructure:"jwt"` DefaultAdmin DefaultAdminConfig `mapstructure:"default_admin"` AdminUsers AdminUsersConfig `mapstructure:"admin_users"` IPRegion IPRegionConfig `mapstructure:"ip_region"` } type JWTConfig struct { Secret string `mapstructure:"secret"` AccessTTL time.Duration `mapstructure:"access_ttl"` } type DefaultAdminConfig struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` DisplayName string `mapstructure:"display_name"` Email string `mapstructure:"email"` } type AdminUsersConfig struct { DefaultPlanCode string `mapstructure:"default_plan_code"` } type IPRegionConfig struct { V4XDBPath string `mapstructure:"v4_xdb_path"` V6XDBPath string `mapstructure:"v6_xdb_path"` } func Load(path string) (*Config, error) { v := viper.New() v.SetConfigFile(path) v.SetEnvPrefix("OPS") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() v.SetDefault("server.port", 8090) v.SetDefault("server.mode", "debug") v.SetDefault("redis.addr", "localhost:6379") v.SetDefault("redis.db", 0) v.SetDefault("cache.driver", "redis") v.SetDefault("log.level", "info") v.SetDefault("log.format", "json") v.SetDefault("jwt.access_ttl", 8*time.Hour) v.SetDefault("default_admin.username", "admin") v.SetDefault("default_admin.display_name", "Administrator") v.SetDefault("admin_users.default_plan_code", "free") v.SetDefault("ip_region.v4_xdb_path", "") v.SetDefault("ip_region.v6_xdb_path", "") if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("read config: %w", err) } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) } if err := applyEnvOverrides(&cfg); err != nil { return nil, err } if cfg.JWT.Secret == "" { return nil, fmt.Errorf("jwt.secret is required (set jwt.secret in yaml or OPS_JWT_SECRET env)") } if cfg.MonitoringDatabase.DBName == "" { cfg.MonitoringDatabase = cfg.Database } return &cfg, nil } func applyEnvOverrides(cfg *Config) error { if v := os.Getenv("OPS_JWT_SECRET"); v != "" { cfg.JWT.Secret = v } if v := os.Getenv("OPS_DEFAULT_ADMIN_USERNAME"); v != "" { cfg.DefaultAdmin.Username = v } if v := os.Getenv("OPS_DEFAULT_ADMIN_PASSWORD"); v != "" { cfg.DefaultAdmin.Password = v } if v := os.Getenv("OPS_DEFAULT_ADMIN_DISPLAY_NAME"); v != "" { cfg.DefaultAdmin.DisplayName = v } if v := os.Getenv("OPS_DEFAULT_ADMIN_EMAIL"); v != "" { cfg.DefaultAdmin.Email = v } if v := os.Getenv("OPS_ADMIN_USERS_DEFAULT_PLAN_CODE"); v != "" { cfg.AdminUsers.DefaultPlanCode = v } if v := os.Getenv("OPS_IP_REGION_V4_XDB_PATH"); v != "" { cfg.IPRegion.V4XDBPath = v } if v := os.Getenv("OPS_IP_REGION_V6_XDB_PATH"); v != "" { cfg.IPRegion.V6XDBPath = v } return nil }