package config import ( "errors" "fmt" "log/slog" "os" "strconv" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger" "github.com/joho/godotenv" ) var ( ErrInvalidDbUrl = errors.New("db url is invalid") ErrInvalidPort = errors.New("port number is invalid") ErrRefreshExpiry = errors.New("refresh token expiry is invalid") ErrAccessExpiry = errors.New("access token expiry is invalid") ErrInvalidJwtKey = errors.New("jwt key is invalid") ErrLogLevel = errors.New("log level not set") ErrInvalidLevel = errors.New("invalid log level") ErrInvalidEnv = errors.New("env not set or invalid") ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid") ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env") ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid") ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid") ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid") ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid") ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid") ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid") ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid") ErrMissingResendApiKey = errors.New("missing Resend Api key") ErrMissingResendSenderEmail = errors.New("missing Resend sender name") ) type AleaPlayConfig struct { Enabled bool `mapstructure:"enabled"` BaseURL string `mapstructure:"base_url"` // "https://api.aleaplay.com" OperatorID string `mapstructure:"operator_id"` // Your operator ID with Alea SecretKey string `mapstructure:"secret_key"` // API secret for signatures GameListURL string `mapstructure:"game_list_url"` // Endpoint to fetch available games DefaultCurrency string `mapstructure:"default_currency"` // "USD", "EUR", etc. SessionTimeout int `mapstructure:"session_timeout"` // In hours } type VeliGamesConfig struct { Enabled bool `mapstructure:"enabled"` APIURL string `mapstructure:"api_url"` OperatorKey string `mapstructure:"operator_key"` SecretKey string `mapstructure:"secret_key"` DefaultCurrency string `mapstructure:"default_currency"` GameIDs struct { Aviator string `mapstructure:"aviator"` } `mapstructure:"game_ids"` } type Config struct { FIXER_API_KEY string FIXER_BASE_URL string BASE_CURRENCY domain.IntCurrency Port int DbUrl string RefreshExpiry int AccessExpiry int JwtKey string LogLevel slog.Level Env string ReportExportPath string `mapstructure:"REPORT_EXPORT_PATH"` AFRO_SMS_API_KEY string AFRO_SMS_SENDER_NAME string AFRO_SMS_RECEIVER_PHONE_NUMBER string ADRO_SMS_HOST_URL string CHAPA_TRANSFER_TYPE string CHAPA_PAYMENT_TYPE string CHAPA_SECRET_KEY string CHAPA_PUBLIC_KEY string CHAPA_BASE_URL string CHAPA_ENCRYPTION_KEY string CHAPA_CALLBACK_URL string CHAPA_RETURN_URL string Bet365Token string PopOK domain.PopOKConfig AleaPlay AleaPlayConfig `mapstructure:"alea_play"` VeliGames VeliGamesConfig `mapstructure:"veli_games"` ResendApiKey string ResendSenderEmail string } func NewConfig() (*Config, error) { config := &Config{} if err := config.loadEnv(); err != nil { return nil, err } return config, nil } func (c *Config) loadEnv() error { err := godotenv.Load() if err != nil && !os.IsNotExist(err) { return errors.New("failed to load env file: " + err.Error()) } env := os.Getenv("ENV") if env == "" { return ErrInvalidEnv } c.Env = env c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH") c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE") c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE") c.FIXER_API_KEY = os.Getenv("FIXER_API_KEY") c.BASE_CURRENCY = domain.IntCurrency(os.Getenv("BASE_CURRENCY")) c.FIXER_BASE_URL = os.Getenv("FIXER_BASE_URL") portStr := os.Getenv("PORT") if portStr == "" { return ErrInvalidPort } port, err := strconv.Atoi(portStr) if err != nil || port < 1 || port > 65535 { return ErrInvalidPort } c.Port = port dbUrl := os.Getenv("DB_URL") if dbUrl == "" { return ErrInvalidDbUrl } c.DbUrl = dbUrl refreshExpiryStr := os.Getenv("REFRESH_EXPIRY") if refreshExpiryStr == "" { return ErrRefreshExpiry } refreshExpiry, err := strconv.Atoi(refreshExpiryStr) if err != nil || refreshExpiry <= 0 { return ErrRefreshExpiry } c.RefreshExpiry = refreshExpiry jwtKey := os.Getenv("JWT_KEY") if jwtKey == "" { return ErrInvalidJwtKey } c.JwtKey = jwtKey accessExpiryStr := os.Getenv("ACCESS_EXPIRY") if accessExpiryStr == "" { return ErrAccessExpiry } accessExpiry, err := strconv.Atoi(accessExpiryStr) if err != nil || accessExpiry <= 0 { return ErrAccessExpiry } c.AccessExpiry = accessExpiry logLevel := os.Getenv("LOG_LEVEL") if logLevel == "" { return ErrLogLevel } lvl, ok := customlogger.LogLevels[logLevel] if !ok { return ErrInvalidLevel } //Chapa c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY") c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY") c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY") c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL") if c.CHAPA_BASE_URL == "" { c.CHAPA_BASE_URL = "https://api.chapa.co/v1" } c.CHAPA_CALLBACK_URL = os.Getenv("CHAPA_CALLBACK_URL") c.CHAPA_RETURN_URL = os.Getenv("CHAPA_RETURN_URL") //Alea Play aleaEnabled := os.Getenv("ALEA_ENABLED") if aleaEnabled == "" { aleaEnabled = "false" // Default disabled } if enabled, err := strconv.ParseBool(aleaEnabled); err != nil { return fmt.Errorf("invalid ALEA_ENABLED value: %w", err) } else { c.AleaPlay.Enabled = enabled } c.AleaPlay.BaseURL = os.Getenv("ALEA_BASE_URL") if c.AleaPlay.BaseURL == "" && c.AleaPlay.Enabled { return errors.New("ALEA_BASE_URL is required when Alea is enabled") } c.AleaPlay.OperatorID = os.Getenv("ALEA_OPERATOR_ID") if c.AleaPlay.OperatorID == "" && c.AleaPlay.Enabled { return errors.New("ALEA_OPERATOR_ID is required when Alea is enabled") } c.AleaPlay.SecretKey = os.Getenv("ALEA_SECRET_KEY") if c.AleaPlay.SecretKey == "" && c.AleaPlay.Enabled { return errors.New("ALEA_SECRET_KEY is required when Alea is enabled") } c.AleaPlay.GameListURL = os.Getenv("ALEA_GAME_LIST_URL") c.AleaPlay.DefaultCurrency = os.Getenv("ALEA_DEFAULT_CURRENCY") if c.AleaPlay.DefaultCurrency == "" { c.AleaPlay.DefaultCurrency = "USD" } sessionTimeoutStr := os.Getenv("ALEA_SESSION_TIMEOUT") if sessionTimeoutStr != "" { timeout, err := strconv.Atoi(sessionTimeoutStr) if err == nil { c.AleaPlay.SessionTimeout = timeout } } //Veli Games veliEnabled := os.Getenv("VELI_ENABLED") if veliEnabled == "" { veliEnabled = "false" // Default to disabled if not specified } if enabled, err := strconv.ParseBool(veliEnabled); err != nil { return fmt.Errorf("invalid VELI_ENABLED value: %w", err) } else { c.VeliGames.Enabled = enabled } apiURL := os.Getenv("VELI_API_URL") if apiURL == "" { apiURL = "https://api.velitech.games" // Default production URL } c.VeliGames.APIURL = apiURL operatorKey := os.Getenv("VELI_OPERATOR_KEY") if operatorKey == "" && c.VeliGames.Enabled { return ErrInvalidVeliOperatorKey } c.VeliGames.OperatorKey = operatorKey secretKey := os.Getenv("VELI_SECRET_KEY") if secretKey == "" && c.VeliGames.Enabled { return ErrInvalidVeliSecretKey } c.VeliGames.SecretKey = secretKey c.VeliGames.GameIDs.Aviator = os.Getenv("VELI_GAME_ID_AVIATOR") defaultCurrency := os.Getenv("VELI_DEFAULT_CURRENCY") if defaultCurrency == "" { defaultCurrency = "USD" // Default currency } c.VeliGames.DefaultCurrency = defaultCurrency c.LogLevel = lvl c.AFRO_SMS_API_KEY = os.Getenv("AFRO_SMS_API_KEY") if c.AFRO_SMS_API_KEY == "" { return ErrInvalidSMSAPIKey } c.AFRO_SMS_SENDER_NAME = os.Getenv("AFRO_SMS_SENDER_NAME") if c.AFRO_SMS_SENDER_NAME == "" { c.AFRO_SMS_SENDER_NAME = "FortuneBet" } c.AFRO_SMS_RECEIVER_PHONE_NUMBER = os.Getenv("AFRO_SMS_RECEIVER_PHONE_NUMBER") c.ADRO_SMS_HOST_URL = os.Getenv("ADRO_SMS_HOST_URL") if c.ADRO_SMS_HOST_URL == "" { c.ADRO_SMS_HOST_URL = "https://api.afrosms.com" } popOKClientID := os.Getenv("POPOK_CLIENT_ID") popOKPlatform := os.Getenv("POPOK_PLATFORM") if popOKClientID == "" { return ErrInvalidPopOKClientID } popOKSecretKey := os.Getenv("POPOK_SECRET_KEY") if popOKSecretKey == "" { return ErrInvalidPopOKSecretKey } popOKBaseURL := os.Getenv("POPOK_BASE_URL") if popOKBaseURL == "" { return ErrInvalidPopOKBaseURL } popOKCallbackURL := os.Getenv("POPOK_CALLBACK_URL") if popOKCallbackURL == "" { return ErrInvalidPopOKCallbackURL } c.PopOK = domain.PopOKConfig{ ClientID: popOKClientID, SecretKey: popOKSecretKey, BaseURL: popOKBaseURL, CallbackURL: popOKCallbackURL, Platform: popOKPlatform, } betToken := os.Getenv("BET365_TOKEN") if betToken == "" { return ErrMissingBetToken } c.Bet365Token = betToken resendApiKey := os.Getenv("RESEND_API_KEY") if resendApiKey == "" { return ErrMissingResendApiKey } c.ResendApiKey = resendApiKey resendSenderEmail := os.Getenv("RESEND_SENDER_EMAIL") if resendSenderEmail == "" { return ErrMissingResendSenderEmail } c.ResendSenderEmail = resendSenderEmail return nil } type ChapaConfig struct { ChapaPaymentType string `mapstructure:"chapa_payment_type"` ChapaTransferType string `mapstructure:"chapa_transfer_type"` }