package config import ( "os" "strings" "github.com/joho/godotenv" ) // Config holds all runtime settings, loaded from environment (.env optional). type Config struct { // Slack SlackBotToken string DefaultSlackChannel string // 모든 이벤트 브로드캐스트 채널 MonitorChannel string // 에러(slog.Error) 발생 시 알림을 보낼 채널 (예: monitor-dev) // Gitea GiteaWebhookSecret string // Notion NotionVerificationToken string NotionAPIToken string NotionAssigneeProperty string // App MappingFile string Addr string // Logging Env string // development | production LogLevel string // debug | info | warn | error LogFormat string // text | json LogFile string // optional file path; empty = stdout only } // Load reads configuration for the given environment and loads its .env files. // // The environment is decided by the run command (the -env flag passed as // envOverride). If empty, it falls back to the APP_ENV variable, then // "development". This keeps env selection command-driven, not ambient. // // Precedence: an earlier-loaded file wins (godotenv.Load never overrides an // already-set variable), and missing files are ignored: // // .env.{env}.local # 개인 비밀값 (git 제외, 선택) // .env.{env} # 환경별 설정 (.env.dev / .env.prod) // .env # 공통 기본값 (fallback) func Load(envOverride string) Config { env := envOverride if env == "" { env = getenv("APP_ENV", "dev") } env = normalizeEnv(env) _ = godotenv.Load(".env." + env + ".local") _ = godotenv.Load(".env." + env) _ = godotenv.Load(".env") return Config{ SlackBotToken: os.Getenv("SLACK_BOT_TOKEN"), DefaultSlackChannel: os.Getenv("DEFAULT_SLACK_CHANNEL"), MonitorChannel: os.Getenv("MONITOR_SLACK_CHANNEL"), GiteaWebhookSecret: os.Getenv("GITEA_WEBHOOK_SECRET"), NotionVerificationToken: os.Getenv("NOTION_VERIFICATION_TOKEN"), NotionAPIToken: os.Getenv("NOTION_API_TOKEN"), NotionAssigneeProperty: getenv("NOTION_ASSIGNEE_PROPERTY", "담당자"), MappingFile: getenv("MAPPING_FILE", "data/mappings.json"), Addr: getenv("ADDR", ":8000"), Env: env, LogLevel: getenv("LOG_LEVEL", defaultLogLevel(env)), LogFormat: getenv("LOG_FORMAT", defaultLogFormat(env)), LogFile: getenv("LOG_FILE", defaultLogFile(env)), } } // normalizeEnv canonicalizes the env name to the short form "dev" or "prod". // Long forms (development/production) and unknown values map sensibly. func normalizeEnv(s string) string { switch strings.ToLower(strings.TrimSpace(s)) { case "prod", "production": return "prod" default: return "dev" } } // IsProduction reports whether the app runs in the production environment. func (c Config) IsProduction() bool { return c.Env == "prod" } func defaultLogLevel(env string) string { if env == "prod" { return "info" } return "debug" } func defaultLogFormat(env string) string { if env == "prod" { return "json" } return "text" } // defaultLogFile decides where logs are written when LOG_FILE is unset: // - dev → "" (콘솔/IDE 출력만) // - prod → 파일로 추출 (logs/app.log) func defaultLogFile(env string) string { if env == "prod" { return "logs/app.log" } return "" } func getenv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }