From 26ff84d70fa3a7f77eb16ab12cee009914e65595 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Tue, 26 May 2026 15:38:08 +0200 Subject: [PATCH] feat: add central config package --- internal/config/config.go | 85 +++++++++++++++++++++++++++++++++++++++ internal/config/module.go | 8 ++++ 2 files changed, 93 insertions(+) create mode 100644 internal/config/config.go create mode 100644 internal/config/module.go diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..cffb1d6 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,85 @@ +package config + +import ( + "errors" + "fmt" + "os" + "strings" +) + +type EpisodeAvailabilityMode string + +const ( + EpisodeAvailabilityModeAuto EpisodeAvailabilityMode = "auto" + EpisodeAvailabilityModeLegacy EpisodeAvailabilityMode = "legacy" + EpisodeAvailabilityModeJikan EpisodeAvailabilityMode = "jikan" +) + +type Config struct { + Port string + + // GinMode maps to gin.SetMode. When empty, the server uses release mode by default. + GinMode string + + DatabaseFile string + + // Allow any Origin for CORS. Intended for local dev / reverse proxy setups only. + CORSAllowAll bool + + EpisodeAvailabilityMode EpisodeAvailabilityMode + + // Optional. When empty, proxy token signing is disabled. + PlaybackProxySecret string + + // Optional debug toggle for Jikan client tracing. + JikanTrace bool +} + +func Load() (Config, error) { + cfg := Config{ + Port: firstNonEmpty(strings.TrimSpace(os.Getenv("PORT")), "3000"), + GinMode: strings.TrimSpace(os.Getenv("GIN_MODE")), + DatabaseFile: firstNonEmpty(strings.TrimSpace(os.Getenv("DATABASE_FILE")), "mal.db"), + CORSAllowAll: strings.TrimSpace(os.Getenv("MAL_CORS_ALLOW_ALL")) == "1", + PlaybackProxySecret: strings.TrimSpace(os.Getenv("PLAYBACK_PROXY_SECRET")), + JikanTrace: truthy(strings.TrimSpace(os.Getenv("MAL_JIKAN_TRACE"))), + EpisodeAvailabilityMode: EpisodeAvailabilityModeAuto, + } + + if raw := strings.ToLower(strings.TrimSpace(os.Getenv("EPISODE_AVAILABILITY_MODE"))); raw != "" { + switch EpisodeAvailabilityMode(raw) { + case EpisodeAvailabilityModeAuto, EpisodeAvailabilityModeLegacy, EpisodeAvailabilityModeJikan: + cfg.EpisodeAvailabilityMode = EpisodeAvailabilityMode(raw) + default: + return Config{}, fmt.Errorf("invalid EPISODE_AVAILABILITY_MODE: %q (expected auto|legacy|jikan)", raw) + } + } + + if strings.TrimSpace(cfg.Port) == "" { + return Config{}, errors.New("PORT must not be empty") + } + if strings.TrimSpace(cfg.DatabaseFile) == "" { + return Config{}, errors.New("DATABASE_FILE must not be empty") + } + + return cfg, nil +} + +func firstNonEmpty(values ...string) string { + for _, v := range values { + if strings.TrimSpace(v) != "" { + return v + } + } + return "" +} + +func truthy(v string) bool { + switch strings.ToLower(strings.TrimSpace(v)) { + case "1", "true", "yes", "y", "on": + return true + default: + return false + } +} + diff --git a/internal/config/module.go b/internal/config/module.go new file mode 100644 index 0000000..7e7a967 --- /dev/null +++ b/internal/config/module.go @@ -0,0 +1,8 @@ +package config + +import "go.uber.org/fx" + +var Module = fx.Options( + fx.Provide(Load), +) +