package config import ( "fmt" "os" "strings" "time" ) // Config holds the application configuration type Config struct { DBHost string DBPort string DBUser string DBPassword string DBName string JWTSecret string ServerPort string GinMode string StoragePath string AccessTokenTTL time.Duration RefreshTokenTTL time.Duration } // Load reads configuration from environment variables with fallbacks func Load() *Config { return &Config{ DBHost: getEnv("DB_HOST", "localhost"), DBPort: getEnv("DB_PORT", "5432"), DBUser: getEnv("DB_USER", "celestia"), DBPassword: getEnv("DB_PASSWORD", "celestia_secret"), DBName: getEnv("DB_NAME", "celestia_trace"), JWTSecret: getEnv("JWT_SECRET", "change-me-in-production"), ServerPort: getEnv("SERVER_PORT", "8080"), GinMode: getEnv("GIN_MODE", "release"), StoragePath: getEnv("STORAGE_PATH", "/data/uploads"), AccessTokenTTL: getDurationEnv("ACCESS_TOKEN_TTL", 30*time.Minute), RefreshTokenTTL: getDurationEnv("REFRESH_TOKEN_TTL", 30*24*time.Hour), } } // Validate prevents production from silently starting with repository defaults. func (c *Config) Validate() error { requiredDatabaseSettings := map[string]string{ "DB_HOST": c.DBHost, "DB_PORT": c.DBPort, "DB_USER": c.DBUser, "DB_PASSWORD": c.DBPassword, "DB_NAME": c.DBName, } for name, value := range requiredDatabaseSettings { if strings.TrimSpace(value) == "" { return fmt.Errorf("%s must not be empty", name) } } if c.GinMode == "release" { weakSecrets := []string{"", "change-me-in-production", "your_jwt_secret_here_change_me"} for _, weak := range weakSecrets { if c.JWTSecret == weak { return fmt.Errorf("JWT_SECRET must be configured with a production secret") } } if len(c.JWTSecret) < 32 { return fmt.Errorf("JWT_SECRET must contain at least 32 characters") } } if strings.TrimSpace(c.StoragePath) == "" { return fmt.Errorf("STORAGE_PATH must not be empty") } return nil } func getEnv(key, fallback string) string { if value, exists := os.LookupEnv(key); exists { return value } return fallback } func getDurationEnv(key string, fallback time.Duration) time.Duration { value, exists := os.LookupEnv(key) if !exists { return fallback } parsed, err := time.ParseDuration(value) if err != nil || parsed <= 0 { return fallback } return parsed }