Yimaru-BackEnd/internal/config/config.go
Samuel Tariku e49ff366d5 feat: Implement wallet notification system and refactor related services
- Added new notification handling in the wallet service to notify admins when wallet balances are low or insufficient.
- Created a new file for wallet notifications and moved relevant functions from the wallet service to this new file.
- Updated the wallet service to publish wallet events including wallet type.
- Refactored the client code to improve readability and maintainability.
- Enhanced the bet handler to support pagination and status filtering for bets.
- Updated routes and handlers for user search functionality to improve clarity and organization.
- Modified cron job scheduling to comment out unused jobs for clarity.
- Updated the WebSocket broadcast to include wallet type in notifications.
- Adjusted the makefile to include Kafka in the docker-compose setup for local development.
2025-09-25 21:26:24 +03:00

495 lines
16 KiB
Go

package config
import (
"errors"
"fmt"
"log/slog"
"os"
"strconv"
"strings"
"time"
"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")
ErrInvalidAtlasBaseUrl = errors.New("Atlas Base URL is invalid")
ErrInvalidAtlasOperatorID = errors.New("Atlas operator ID is invalid")
ErrInvalidAtlasSecretKey = errors.New("Atlas secret key is invalid")
ErrInvalidAtlasBrandID = errors.New("Atlas brand ID is invalid")
ErrInvalidAtlasPartnerID = errors.New("Atlas Partner ID is invalid")
ErrMissingResendApiKey = errors.New("missing Resend Api key")
ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
ErrMissingTwilioAccountSid = errors.New("missing twilio account sid")
ErrMissingTwilioAuthToken = errors.New("missing twilio auth token")
ErrMissingTwilioSenderPhoneNumber = errors.New("missing twilio sender phone number")
)
type EnetPulseConfig struct {
UserName string `mapstructure:"username"` // "https://api.aleaplay.com"
Token string `mapstructure:"token"` // Your operator ID with Alea
}
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 VeliConfig struct {
APIKey string `mapstructure:"VELI_API_KEY"`
BaseURL string `mapstructure:"VELI_BASE_URL"`
SecretKey string `mapstructure:"VELI_SECRET_KEY"`
OperatorID string `mapstructure:"VELI_OPERATOR_ID"`
BrandID string `mapstructure:"VELI_BRAND_ID"`
Currency string `mapstructure:"VELI_DEFAULT_CURRENCY"`
WebhookURL string `mapstructure:"VELI_WEBHOOK_URL"`
Enabled bool `mapstructure:"Enabled"`
}
type AtlasConfig struct {
BaseURL string `mapstructure:"ATLAS_BASE_URL"`
SecretKey string `mapstructure:"ATLAS_SECRET_KEY"`
OperatorID string `mapstructure:"ATLAS_OPERATOR_ID"`
CasinoID string `mapstructure:"ATLAS_BRAND_ID"`
PartnerID string `mapstructure:"ATLAS_PARTNER_ID"`
}
type ARIFPAYConfig struct {
APIKey string `mapstructure:"ARIFPAY_API_KEY"`
BaseURL string `mapstructure:"ARIFPAY_BASE_URL"`
// Default URLs
CancelUrl string `mapstructure:"cancelUrl"`
SuccessUrl string `mapstructure:"successUrl"`
ErrorUrl string `mapstructure:"errorUrl"`
B2CNotifyUrl string `mapstructure:"notifyUrl"`
C2BNotifyUrl string `mapstructure:"notifyUrl"`
// Default Payment Configs
PaymentMethods []string `mapstructure:"paymentMethods"`
ExpireDate string `mapstructure:"expireDate"`
ItemName string `mapstructure:"name"`
Quantity int `mapstructure:"quantity"`
Description string `mapstructure:"description"`
BeneficiaryAccountNumber string `mapstructure:"accountNumber"`
Bank string `mapstructure:"bank"`
Lang string `mapstructure:"amount"`
}
type SANTIMPAYConfig struct {
SecretKey string `mapstructure:"secret_key"`
MerchantID string `mapstructure:"merchantId"`
BaseURL string `mapstructure:"base_url"`
NotifyURL string `mapstructure:"notifyUrl"`
CancelUrl string `mapstructure:"cancelUrl"`
SuccessUrl string `mapstructure:"successUrl"`
}
type TELEBIRRConfig struct {
TelebirrFabricAppID string `mapstructure:"fabric_app_id"`
TelebirrAppSecret string `mapstructure:"appSecret"`
TelebirrBaseURL string `mapstructure:"base_url"`
TelebirrMerchantCode string `mapstructure:"merchant_code"`
TelebirrCallbackURL string `mapstructure:"callback_url"`
}
type Config struct {
FIXER_API_KEY string
FIXER_BASE_URL string
BASE_CURRENCY domain.IntCurrency
Port int
Service string
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
EnetPulseConfig EnetPulseConfig
PopOK domain.PopOKConfig
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
Atlas AtlasConfig `mapstructure:"atlas"`
VeliGames VeliConfig `mapstructure:"veli_games"`
ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"`
SANTIMPAY SANTIMPAYConfig `mapstructure:"santimpay_config"`
TELEBIRR TELEBIRRConfig `mapstructure:"telebirr_config"`
ResendApiKey string
ResendSenderEmail string
TwilioAccountSid string
TwilioAuthToken string
TwilioSenderPhoneNumber string
RedisAddr string
KafkaBrokers []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.RedisAddr = os.Getenv("REDIS_ADDR")
c.KafkaBrokers = strings.Split(os.Getenv("KAFKA_BROKERS"), ",")
c.EnetPulseConfig.Token = os.Getenv("ENETPULSE_TOKEN")
c.EnetPulseConfig.UserName = os.Getenv("ENETPULSE_USERNAME")
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
}
//Telebirr
c.TELEBIRR.TelebirrBaseURL = os.Getenv("TELEBIRR_BASE_URL")
c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_APP_SECRET")
c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_FABRIC_APP_ID")
c.TELEBIRR.TelebirrMerchantCode = os.Getenv("TELEBIRR_MERCHANT_CODE")
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
//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")
c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY")
c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL")
c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL")
c.ARIFPAY.C2BNotifyUrl = os.Getenv("ARIFPAY_C2B_NOTIFY_URL")
c.ARIFPAY.B2CNotifyUrl = os.Getenv("ARIFPAY_B2C_NOTIFY_URL")
c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL")
c.ARIFPAY.BaseURL = os.Getenv("ARIFPAY_BASE_URL")
c.ARIFPAY.Bank = os.Getenv("ARIFPAY_BANK")
c.ARIFPAY.BeneficiaryAccountNumber = os.Getenv("ARIFPAY_BENEFICIARY_ACCOUNT_NUMBER")
c.ARIFPAY.Description = os.Getenv("ARIFPAY_DESCRIPTION")
c.ARIFPAY.ExpireDate = time.Now().Add(time.Hour * 3).Format("2006-01-02")
c.ARIFPAY.ItemName = os.Getenv("ARIFPAY_ITEM_NAME")
c.ARIFPAY.Lang = "EN"
c.ARIFPAY.Quantity = 1
c.ARIFPAY.PaymentMethods = []string{"TELEBIRR", "AWAASH", "AWAASH_WALLET", "PSS", "CBE", "AMOLE", "BOA", "KACHA", "TELEBIRR", "HELLOCASH", "MPESSA"}
c.SANTIMPAY.SecretKey = os.Getenv("SANTIMPAY_SECRET_KEY")
c.SANTIMPAY.MerchantID = os.Getenv("SANTIMPAY_MERCHANT_ID")
c.SANTIMPAY.BaseURL = os.Getenv("SANTIMPAY_BASE_URL")
c.SANTIMPAY.NotifyURL = os.Getenv("SANTIMPAY_NOTIFY_URL")
c.SANTIMPAY.CancelUrl = os.Getenv("SANTIMPAY_CANCEL_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 = "true" // Default to disabled if not specified
}
// veliOperatorID := os.Getenv("VELI_OPERATOR_ID")
// veliBrandID := os.Getenv("VELI_BRAND_ID")
c.VeliGames.OperatorID = os.Getenv("VELI_OPERATOR_ID")
c.VeliGames.BrandID = os.Getenv("VELI_BRAND_ID")
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_BASE_URL")
// if apiURL == "" {
// apiURL = "https://api.velitech.games" // Default production URL
// }
c.VeliGames.BaseURL = os.Getenv("VELI_BASE_URL")
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,
}
AtlasBaseUrl := os.Getenv("ATLAS_BASE_URL")
if AtlasBaseUrl == "" {
return ErrInvalidAtlasBaseUrl
}
AtlasSecretKey := os.Getenv("ATLAS_SECRET_KEY")
if AtlasSecretKey == "" {
return ErrInvalidAtlasSecretKey
}
AtlasBrandID := os.Getenv("ATLAS_BRAND_ID")
if AtlasBrandID == "" {
return ErrInvalidAtlasBrandID
}
AtlasPartnerID := os.Getenv("ATLAS_PARTNER_ID")
if AtlasPartnerID == "" {
return ErrInvalidAtlasPartnerID
}
AtlasOperatorID := os.Getenv("ATLAS_OPERATOR_ID")
if AtlasOperatorID == "" {
return ErrInvalidAtlasOperatorID
}
c.Atlas = AtlasConfig{
BaseURL: AtlasBaseUrl,
SecretKey: AtlasSecretKey,
CasinoID: AtlasBrandID,
PartnerID: AtlasPartnerID,
OperatorID: AtlasOperatorID,
}
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
twilioAccountSid := os.Getenv("TWILIO_ACCOUNT_SID")
if twilioAccountSid == "" {
return ErrMissingTwilioAccountSid
}
c.TwilioAccountSid = twilioAccountSid
twilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
if twilioAuthToken == "" {
return ErrMissingTwilioAuthToken
}
c.TwilioAuthToken = twilioAuthToken
twilioSenderPhoneNumber := os.Getenv("TWILIO_SENDER_PHONE_NUMBER")
if twilioSenderPhoneNumber == "" {
return ErrMissingTwilioSenderPhoneNumber
}
c.TwilioSenderPhoneNumber = twilioSenderPhoneNumber
return nil
}
type ChapaConfig struct {
ChapaPaymentType string `mapstructure:"chapa_payment_type"`
ChapaTransferType string `mapstructure:"chapa_transfer_type"`
}