package config import ( "errors" "fmt" "strings" "time" "github.com/rs/zerolog" "github.com/spf13/viper" ) // AppConfig contains the application configuration type AppConfig struct { Env string // Server configuration Port int // Application configuration Name string Environment string VendorLockTimeOut int Database DatabaseConfig Redis RedisConfig Server ServerConfig RabbitMQ RabbitMQConfig Syslog SyslogConfig Metrics MetricsConfig AzureCommunicationConfig AzureCommunicationConfig AzureBlobStorage AzureBlobStorageConfig AzureServiceBus AzureServiceBusConfig PgDatabaseConfig PgDatabaseConfig JWT JWTConfig OAuth OAuthConfig } // PrintConfig logs the current configuration func PrintConfig(logger zerolog.Logger, config *AppConfig) { logger. Info(). Str("port", config.Server.WebPort). Str("name", config.Name). Str("environment", config.Environment). Msg("Application configuration") } // NewConfig creates a new configuration from environment variables or config file func NewConfig() (*AppConfig, error) { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath(".") viper.AddConfigPath("./config") // Enable automatic environment variable binding viper.AutomaticEnv() // Set environment variable prefix and replace dots with underscores viper.SetEnvPrefix("base") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Set defaults viper.SetDefault("environment", "local") viper.SetDefault("name", "base") viper.SetDefault("debug", true) viper.SetDefault("port", 8080) viper.SetDefault("vendor_lock_time_out", 5) // Database defaults viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", "3306") viper.SetDefault("database.user", "base") viper.SetDefault("database.password", "base123") viper.SetDefault("database.name", "base") viper.SetDefault("database.max_idle_conns", 10) viper.SetDefault("database.max_open_conns", 20) viper.SetDefault("database.conn_max_idle_time", "2m") viper.SetDefault("database.conn_max_lifetime", "9m") viper.SetDefault("database.ro_user", "base") viper.SetDefault("database.ro_password", "base123") viper.SetDefault("database.ro_max_idle_conns", 10) viper.SetDefault("database.ro_max_open_conns", 20) // PgDatabase defaults viper.SetDefault("pg_database.host", "localhost") viper.SetDefault("pg_database.port", 5430) viper.SetDefault("pg_database.user", "alinmeuser") viper.SetDefault("pg_database.password", "password") viper.SetDefault("pg_database.name", "alinmedb") viper.SetDefault("pg_database.ssl_mode", "disable") viper.SetDefault("pg_database.connection_timeout", 1*time.Minute) viper.SetDefault("pg_database.query_timeout", 60*time.Second) viper.SetDefault("pg_database.pool_config.max_conn", 20) viper.SetDefault("pg_database.pool_config.min_conn", 5) viper.SetDefault("pg_database.pool_config.max_conn_lifetime", 5*time.Minute) viper.SetDefault("pg_database.pool_config.max_conn_idle_time", 30*time.Minute) viper.SetDefault("pg_database.migrations.enabled", true) viper.SetDefault("pg_database.migrations.dir", "./database/migrations") // Redis defaults viper.SetDefault("redis.host", "localhost") viper.SetDefault("redis.port", "6379") viper.SetDefault("redis.password", "") viper.SetDefault("redis.database", 0) viper.SetDefault("redis.url", "") // Server defaults viper.SetDefault("server.host", "localhost") viper.SetDefault("server.port", "8080") viper.SetDefault("rpc.host", "0.0.0.0") viper.SetDefault("rpc.port", "8102") viper.SetDefault("web.host", "localhost") viper.SetDefault("web.port", "8101") viper.SetDefault("server.max_file_size_mb", 10) // Default 10MB file size limit viper.SetDefault("server.cleanup_queues", false) // Default to false for production safety // JWT defaults viper.SetDefault("jwt.secret", "default-secret-key-change-in-production") viper.SetDefault("jwt.access_token_expiration", "24h") viper.SetDefault("jwt.refresh_token_expiration", "168h") // 7 days // OAuth defaults viper.SetDefault("oauth.google.client_id", "") viper.SetDefault("oauth.google.client_secret", "") viper.SetDefault("oauth.google.redirect_url", "") viper.SetDefault("oauth.google.scopes", []string{"openid", "profile", "email"}) viper.SetDefault("oauth.github.client_id", "") viper.SetDefault("oauth.github.client_secret", "") viper.SetDefault("oauth.github.redirect_url", "") viper.SetDefault("oauth.github.scopes", []string{"user:email"}) viper.SetDefault("oauth.linkedin.client_id", "") viper.SetDefault("oauth.linkedin.client_secret", "") viper.SetDefault("oauth.linkedin.redirect_url", "") viper.SetDefault("oauth.linkedin.scopes", []string{"r_liteprofile", "r_emailaddress"}) viper.SetDefault("oauth.mock.enabled", false) viper.SetDefault("oauth.mock.base_url", "http://localhost:9999") viper.SetDefault("oauth.mock.client_id", "mock-client") viper.SetDefault("oauth.mock.client_secret", "mock-secret") viper.SetDefault("oauth.mock.redirect_url", "http://localhost:3000/auth/callback") // RabbitMQ defaults viper.SetDefault("rabbitmq.host", "localhost") viper.SetDefault("rabbitmq.port", "5678") viper.SetDefault("rabbitmq.user", "root") viper.SetDefault("rabbitmq.password", "root") viper.SetDefault("rabbitmq.vhost", "/") viper.SetDefault("rabbitmq.exchange", "base_exchange") viper.SetDefault("rabbitmq.internal_exchange", "base_internal_exchange") viper.SetDefault("rabbitmq.max_channels", "1") viper.SetDefault("rabbitmq.max_retry_attempts", 3) viper.SetDefault("rabbitmq.prefetch_count", 5) viper.SetDefault("rabbitmq.retry_ttl", "10s") // BarcodeMapping defaults viper.SetDefault("barcodemapping.host", "localhost") viper.SetDefault("barcodemapping.port", "8081") viper.SetDefault("barcodemapping.cache", false) viper.SetDefault("barcodemapping.prefix", "barcodemapping") viper.SetDefault("barcodemapping.ttl", "5m") // Discount defaults viper.SetDefault("discount.url", "http://localhost") viper.SetDefault("discount.port", 8082) viper.SetDefault("discount.cache", false) viper.SetDefault("discount.prefix", "discount") viper.SetDefault("discount.ttl", "30s") // Syslog defaults viper.SetDefault("syslog.host", "localhost") viper.SetDefault("syslog.port", "1514") viper.SetDefault("syslog.protocol", "udp") viper.SetDefault("syslog.log_level", "INFO") // Auth defaults viper.SetDefault("vendorauth.url", "") viper.SetDefault("vendorauth.client_id", "base") viper.SetDefault("vendorauth.client_secret", "") viper.SetDefault("vendorauth.cache", false) viper.SetDefault("vendorauth.prefix", "vendorauth") viper.SetDefault("vendorauth.ttl", "5m") viper.SetDefault("authshield.url", "http://chart-shield.auth-shield.svc.cluster.local/auth-shield/authorize") viper.SetDefault("authshield.cache", false) viper.SetDefault("authshield.prefix", "authshield") viper.SetDefault("authshield.ttl", "5m") // Metrics defaults viper.SetDefault("metrics.enabled", true) viper.SetDefault("metrics.service_name", "base") viper.SetDefault("metrics.path", "/metrics") viper.SetDefault("metrics.port", "8080") // Bind environment variables explicitly for better control bindEnvVars() // Try to read config file, but don't fail if it doesn't exist if err := viper.ReadInConfig(); err != nil { var configFileNotFoundError viper.ConfigFileNotFoundError if errors.As(err, &configFileNotFoundError) { fmt.Println("Config file not found, using environment variables and defaults") } else { return nil, fmt.Errorf("failed to read config file: %w", err) } } config := &AppConfig{ Name: viper.GetString("name"), Environment: viper.GetString("environment"), Port: viper.GetInt("port"), VendorLockTimeOut: viper.GetInt("vendor_lock_time_out"), RabbitMQ: RabbitMQConfig{ Host: viper.GetString("rabbitmq.host"), Port: viper.GetInt("rabbitmq.port"), User: viper.GetString("rabbitmq.user"), Password: viper.GetString("rabbitmq.password"), VHost: viper.GetString("rabbitmq.vhost"), MaxConnections: viper.GetInt("rabbitmq.max_connections"), MaxChannels: viper.GetInt("rabbitmq.max_channels"), ConnectionTimeout: viper.GetDuration("rabbitmq.connection_timeout"), HeartbeatInterval: viper.GetDuration("rabbitmq.heartbeat_interval"), ReconnectDelay: viper.GetDuration("rabbitmq.reconnect_delay"), MaxReconnectDelay: viper.GetDuration("rabbitmq.max_reconnect_delay"), ReconnectAttempts: viper.GetInt("rabbitmq.reconnect_attempts"), EnableAutoReconnect: viper.GetBool("rabbitmq.enable_auto_reconnect"), LogLevel: viper.GetString("rabbitmq.log_level"), MaxRetryAttempts: viper.GetInt("rabbitmq.max_retry_attempts"), PrefetchCount: viper.GetInt("rabbitmq.prefetch_count"), RetryTTL: viper.GetDuration("rabbitmq.retry_ttl"), }, Database: DatabaseConfig{ User: viper.GetString("database.user"), Password: viper.GetString("database.password"), Host: viper.GetString("database.host"), Port: viper.GetString("database.port"), Name: viper.GetString("database.name"), MaxIdleConns: viper.GetInt("database.max_idle_conns"), MaxOpenConns: viper.GetInt("database.max_open_conns"), ConnMaxIdleTime: viper.GetString("database.conn_max_idle_time"), ConnMaxLifetime: viper.GetString("database.conn_max_lifetime"), }, Redis: RedisConfig{ Host: viper.GetString("redis.host"), Port: viper.GetString("redis.port"), Password: viper.GetString("redis.password"), Database: viper.GetInt("redis.database"), URL: viper.GetString("redis.url"), }, Server: ServerConfig{ Domain: viper.GetString("server.host"), HTTPPort: viper.GetString("server.port"), GRPCPort: viper.GetString("server.port"), RPCHost: viper.GetString("rpc.host"), RPCPort: viper.GetString("rpc.port"), WebHost: viper.GetString("web.host"), WebPort: viper.GetString("web.port"), MaxFileSizeMB: viper.GetInt("server.max_file_size_mb"), CleanupQueues: viper.GetBool("server.cleanup_queues"), JWTSecret: viper.GetString("server.jwt_secret"), }, Syslog: SyslogConfig{ Host: viper.GetString("syslog.host"), Port: viper.GetString("syslog.port"), Protocol: viper.GetString("syslog.protocol"), LogLevel: viper.GetString("syslog.log_level"), }, Metrics: MetricsConfig{ Enabled: viper.GetBool("metrics.enabled"), ServiceName: viper.GetString("metrics.service_name"), Path: viper.GetString("metrics.path"), Port: viper.GetString("metrics.port"), }, AzureCommunicationConfig: AzureCommunicationConfig{ Endpoint: GetStringOrDefault("email.endpoint", ""), AccessKey: GetStringOrDefault("email.access_key", ""), ApiVersion: GetStringOrDefault("email.api_version", "2025-03-01"), SenderAddress: GetStringOrDefault("email.sender_address", "no-reply@alinme.com"), }, AzureBlobStorage: AzureBlobStorageConfig{ BlobEndpoint: GetStringOrDefault("azure_blob_storage.blob_endpoint", "https://alinmestorage.blob.core.windows.net"), }, PgDatabaseConfig: PgDatabaseConfig{ Host: viper.GetString("pg_database.host"), Port: viper.GetInt("pg_database.port"), User: viper.GetString("pg_database.user"), Password: viper.GetString("pg_database.password"), Name: viper.GetString("pg_database.name"), SSLMode: viper.GetString("pg_database.ssl_mode"), ConnectionTimeout: viper.GetDuration("pg_database.connection_timeout"), QueryTimeout: viper.GetDuration("pg_database.query_timeout"), PoolConfig: PgPoolConfig{ MaxConn: viper.GetInt32("pg_database.pool_config.max_conn"), MinConn: viper.GetInt32("pg_database.pool_config.min_conn"), MaxConnLifetime: viper.GetDuration("pg_database.pool_config.max_conn_lifetime"), MaxConnIdleTime: viper.GetDuration("pg_database.pool_config.max_conn_idle_time"), }, Migrations: MigrationsConfig{ Enabled: viper.GetBool("pg_database.migrations.enabled"), Dir: viper.GetString("pg_database.migrations.dir"), }, }, JWT: func() JWTConfig { // Use jwt.secret if set, otherwise fall back to server.jwt_secret for backward compatibility secret := viper.GetString("jwt.secret") if secret == "" || secret == "default-secret-key-change-in-production" { if serverSecret := viper.GetString("server.jwt_secret"); serverSecret != "" { secret = serverSecret } } return JWTConfig{ Secret: secret, AccessTokenExpiration: viper.GetDuration("jwt.access_token_expiration"), RefreshTokenExpiration: viper.GetDuration("jwt.refresh_token_expiration"), } }(), OAuth: OAuthConfig{ Google: OAuthProviderConfig{ ClientID: viper.GetString("oauth.google.client_id"), ClientSecret: viper.GetString("oauth.google.client_secret"), RedirectURL: viper.GetString("oauth.google.redirect_url"), Scopes: viper.GetStringSlice("oauth.google.scopes"), }, GitHub: OAuthProviderConfig{ ClientID: viper.GetString("oauth.github.client_id"), ClientSecret: viper.GetString("oauth.github.client_secret"), RedirectURL: viper.GetString("oauth.github.redirect_url"), Scopes: viper.GetStringSlice("oauth.github.scopes"), }, LinkedIn: OAuthProviderConfig{ ClientID: viper.GetString("oauth.linkedin.client_id"), ClientSecret: viper.GetString("oauth.linkedin.client_secret"), RedirectURL: viper.GetString("oauth.linkedin.redirect_url"), Scopes: viper.GetStringSlice("oauth.linkedin.scopes"), }, Mock: OAuthMockConfig{ Enabled: viper.GetBool("oauth.mock.enabled"), BaseURL: viper.GetString("oauth.mock.base_url"), OAuthProviderConfig: OAuthProviderConfig{ ClientID: viper.GetString("oauth.mock.client_id"), ClientSecret: viper.GetString("oauth.mock.client_secret"), RedirectURL: viper.GetString("oauth.mock.redirect_url"), Scopes: viper.GetStringSlice("oauth.mock.scopes"), }, }, }, } return config, nil } // bindEnvVars explicitly binds environment variables for better control func bindEnvVars() { // Application level BindEnv("environment", "base_ENVIRONMENT") BindEnv("name", "APP_NAME") BindEnv("debug", "base_DEBUG") BindEnv("port", "base_PORT") BindEnv("vendor_lock_time_out", "base_VENDOR_LOCK_TIMEOUT") // Database BindEnv("database.host", "base_DB_HOST") BindEnv("database.port", "base_DB_PORT") BindEnv("database.user", "base_DB_USERNAME") BindEnv("database.password", "base_DB_PASSWORD") BindEnv("database.name", "base_DB_NAME") BindEnv("database.max_idle_conns", "base_DB_MAX_IDLE_CONNS") BindEnv("database.max_open_conns", "base_DB_MAX_OPEN_CONNS") BindEnv("database.conn_max_idle_time", "base_DB_CONN_MAX_IDLE_TIME") BindEnv("database.conn_max_lifetime", "base_DB_CONN_MAX_LIFETIME") BindEnv("database.ro_user", "base_DB_RO_USERNAME") BindEnv("database.ro_password", "base_DB_RO_PASSWORD") BindEnv("database.ro_max_idle_conns", "base_DB_RO_MAX_IDLE_CONNS") BindEnv("database.ro_max_open_conns", "base_DB_RO_MAX_OPEN_CONNS") // Redis BindEnv("redis.host", "base_REDIS_HOST") BindEnv("redis.port", "base_REDIS_PORT") BindEnv("redis.password", "base_REDIS_PASSWORD") BindEnv("redis.database", "base_REDIS_DATABASE") BindEnv("redis.url", "base_REDIS_URL") // Server BindEnv("server.host", "base_SERVER_HOST") BindEnv("server.port", "base_SERVER_PORT") BindEnv("rpc.host", "base_RPC_HOST") BindEnv("rpc.port", "base_RPC_PORT") BindEnv("web.host", "base_WEB_HOST") BindEnv("web.port", "base_WEB_PORT") BindEnv("server.max_file_size_mb", "base_SERVER_MAX_FILE_SIZE_MB") BindEnv("server.cleanup_queues", "base_SERVER_CLEANUP_QUEUES") BindEnv("server.jwt_secret", "base_SERVER_JWT_SECRET") // JWT BindEnv("jwt.secret", "base_JWT_SECRET") BindEnv("jwt.access_token_expiration", "base_JWT_ACCESS_TOKEN_EXPIRATION") BindEnv("jwt.refresh_token_expiration", "base_JWT_REFRESH_TOKEN_EXPIRATION") // OAuth BindEnv("oauth.google.client_id", "base_OAUTH_GOOGLE_CLIENT_ID") BindEnv("oauth.google.client_secret", "base_OAUTH_GOOGLE_CLIENT_SECRET") BindEnv("oauth.google.redirect_url", "base_OAUTH_GOOGLE_REDIRECT_URL") BindEnv("oauth.github.client_id", "base_OAUTH_GITHUB_CLIENT_ID") BindEnv("oauth.github.client_secret", "base_OAUTH_GITHUB_CLIENT_SECRET") BindEnv("oauth.github.redirect_url", "base_OAUTH_GITHUB_REDIRECT_URL") BindEnv("oauth.linkedin.client_id", "base_OAUTH_LINKEDIN_CLIENT_ID") BindEnv("oauth.linkedin.client_secret", "base_OAUTH_LINKEDIN_CLIENT_SECRET") BindEnv("oauth.linkedin.redirect_url", "base_OAUTH_LINKEDIN_REDIRECT_URL") BindEnv("oauth.mock.enabled", "base_OAUTH_MOCK_ENABLED") BindEnv("oauth.mock.base_url", "base_OAUTH_MOCK_BASE_URL") BindEnv("oauth.mock.client_id", "base_OAUTH_MOCK_CLIENT_ID") BindEnv("oauth.mock.client_secret", "base_OAUTH_MOCK_CLIENT_SECRET") BindEnv("oauth.mock.redirect_url", "base_OAUTH_MOCK_REDIRECT_URL") // RabbitMQ BindEnv("rabbitmq.host", "base_RABBITMQ_HOST") BindEnv("rabbitmq.port", "base_RABBITMQ_PORT") BindEnv("rabbitmq.user", "base_RABBIT_USERNAME") BindEnv("rabbitmq.password", "base_RABBIT_PASSWORD") BindEnv("rabbitmq.vhost", "base_RABBITMQ_VHOST") BindEnv("rabbitmq.max_connections", "base_RABBITMQ_MAX_CONNECTIONS") BindEnv("rabbitmq.max_channels", "base_RABBITMQ_MAX_CHANNELS") BindEnv("rabbitmq.connection_timeout", "base_RABBITMQ_CONNECTION_TIMEOUT") BindEnv("rabbitmq.heartbeat_interval", "base_RABBITMQ_HEARTBEAT_INTERVAL") BindEnv("rabbitmq.reconnect_delay", "base_RABBITMQ_RECONNECT_DELAY") BindEnv("rabbitmq.max_reconnect_delay", "base_RABBITMQ_MAX_RECONNECT_DELAY") BindEnv("rabbitmq.reconnect_attempts", "base_RABBITMQ_RECONNECT_ATTEMPTS") BindEnv("rabbitmq.enable_auto_reconnect", "base_RABBITMQ_ENABLE_AUTO_RECONNECT") BindEnv("rabbitmq.log_level", "base_RABBITMQ_LOG_LEVEL") BindEnv("rabbitmq.max_retry_attempts", "base_RABBITMQ_MAX_RETRY_ATTEMPTS") BindEnv("rabbitmq.prefetch_count", "base_RABBITMQ_PREFETCH_COUNT") BindEnv("rabbitmq.retry_ttl", "base_RABBITMQ_RETRY_TTL") // Azure Blob Storage BindEnv("azure_blob_storage.blob_endpoint", "base_AZURE_BLOB_STORAGE_BLOB_ENDPOINT") // Azure Service Bus BindEnv("azure_service_bus.connection_string", "base_AZURE_SERVICE_BUS_CONNECTION_STRING") BindEnv("azure_service_bus.namespace", "base_AZURE_SERVICE_BUS_NAMESPACE") BindEnv("azure_service_bus.use_managed_identity", "base_AZURE_SERVICE_BUS_USE_MANAGED_IDENTITY") // BarcodeMapping BindEnv("barcodemapping.host", "base_BARCODEMAPPING_HOST") BindEnv("barcodemapping.port", "base_BARCODEMAPPING_PORT") BindEnv("barcodemapping.cache", "base_BARCODEMAPPING_CACHE") BindEnv("barcodemapping.ttl", "base_BARCODEMAPPING_TTL") BindEnv("barcodemapping.prefix", "base_BARCODEMAPPING_PREFIX") // Discount BindEnv("discount.url", "base_DISCOUNT_URL") BindEnv("discount.port", "base_DISCOUNT_PORT") BindEnv("discount.cache", "base_DISCOUNT_CACHE") BindEnv("discount.ttl", "base_DISCOUNT_TTL") BindEnv("discount.prefix", "base_DISCOUNT_PREFIX") // Syslog BindEnv("syslog.host", "base_SYSLOG_HOST") BindEnv("syslog.port", "base_SYSLOG_PORT") BindEnv("syslog.protocol", "base_SYSLOG_PROTOCOL") BindEnv("syslog.log_level", "base_SYSLOG_LOG_LEVEL") // Auth BindEnv("vendorauth.url", "base_VENDOR_AUTH_URL") BindEnv("vendorauth.client_id", "base_VENDOR_AUTH_CLIENT_ID") BindEnv("vendorauth.client_secret", "base_VENDOR_AUTH_SECRET") BindEnv("vendorauth.cache", "base_VENDOR_AUTH_CACHE") BindEnv("vendorauth.prefix", "base_VENDOR_AUTH_PREFIX") BindEnv("vendorauth.ttl", "base_VENDOR_AUTH_TTL") BindEnv("authshield.url", "base_AUTH_SHIELD_URL") BindEnv("authshield.cache", "base_AUTH_SHIELD_CACHE") BindEnv("authshield.prefix", "base_AUTH_SHIELD_PREFIX") BindEnv("authshield.ttl", "base_AUTH_SHIELD_TTL") // Metrics BindEnv("metrics.enabled", "base_METRICS_ENABLED") BindEnv("metrics.service_name", "base_METRICS_SERVICE_NAME") BindEnv("metrics.path", "base_METRICS_PATH") BindEnv("metrics.port", "base_METRICS_PORT") // PgDatabase BindEnv("pg_database.host", "base_PG_DATABASE_HOST") BindEnv("pg_database.port", "base_PG_DATABASE_PORT") BindEnv("pg_database.user", "base_PG_DATABASE_USER") BindEnv("pg_database.password", "base_PG_DATABASE_PASSWORD") BindEnv("pg_database.name", "base_PG_DATABASE_NAME") BindEnv("pg_database.ssl_mode", "base_PG_DATABASE_SSL_MODE") BindEnv("pg_database.connection_timeout", "base_PG_DATABASE_CONNECTION_TIMEOUT") BindEnv("pg_database.query_timeout", "base_PG_DATABASE_QUERY_TIMEOUT") BindEnv("pg_database.pool_config.max_conn", "base_PG_DATABASE_POOL_CONFIG_MAX_CONN") BindEnv("pg_database.pool_config.min_conn", "base_PG_DATABASE_POOL_CONFIG_MIN_CONN") BindEnv("pg_database.pool_config.max_conn_lifetime", "base_PG_DATABASE_POOL_CONFIG_MAX_CONN_LIFETIME") BindEnv("pg_database.pool_config.max_conn_idle_time", "base_PG_DATABASE_POOL_CONFIG_MAX_CONN_IDLE_TIME") BindEnv("pg_database.migrations.enabled", "base_PG_DATABASE_MIGRATIONS_ENABLED") BindEnv("pg_database.migrations.dir", "base_PG_DATABASE_MIGRATIONS_DIR") } func BindEnv(input, envKey string) { if err := viper.BindEnv(input, envKey); err != nil { panic(err) } } func GetStringOrDefault(key string, defaultValue string) string { if viper.IsSet(key) { return viper.GetString(key) } return defaultValue }