package domain import ( "errors" "fmt" "strconv" "strings" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "go.uber.org/zap" ) var ( ErrSettingNotFound = errors.New("cannot find setting in list") ) type SettingList struct { SMSProvider SMSProvider `json:"sms_provider"` MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` BetAmountLimit Currency `json:"bet_amount_limit"` DailyTicketPerIP int64 `json:"daily_ticket_limit"` TotalWinningLimit Currency `json:"total_winning_limit"` AmountForBetReferral Currency `json:"amount_for_bet_referral"` CashbackAmountCap Currency `json:"cashback_amount_cap"` DefaultWinningLimit int64 `json:"default_winning_limit"` ReferralRewardAmount Currency `json:"referral_reward_amount"` CashbackPercentage float32 `json:"cashback_percentage"` DefaultMaxReferrals int64 `json:"default_max_referrals"` MinimumBetAmount Currency `json:"minimum_bet_amount"` BetDuplicateLimit int64 `json:"bet_duplicate_limit"` SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"` SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"` WelcomeBonusActive bool `json:"welcome_bonus_active"` WelcomeBonusMultiplier float32 `json:"welcome_bonus_multiplier"` WelcomeBonusCap Currency `json:"welcome_bonus_cap"` WelcomeBonusCount int64 `json:"welcome_bonus_count"` WelcomeBonusExpire int64 `json:"welcome_bonus_expiry"` } type SettingListRes struct { SMSProvider SMSProvider `json:"sms_provider"` MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` BetAmountLimit float32 `json:"bet_amount_limit"` DailyTicketPerIP int64 `json:"daily_ticket_limit"` TotalWinningLimit float32 `json:"total_winning_limit"` AmountForBetReferral float32 `json:"amount_for_bet_referral"` CashbackAmountCap float32 `json:"cashback_amount_cap"` DefaultWinningLimit int64 `json:"default_winning_limit"` ReferralRewardAmount float32 `json:"referral_reward_amount"` CashbackPercentage float32 `json:"cashback_percentage"` DefaultMaxReferrals int64 `json:"default_max_referrals"` MinimumBetAmount float32 `json:"minimum_bet_amount"` BetDuplicateLimit int64 `json:"bet_duplicate_limit"` SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"` SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"` WelcomeBonusActive bool `json:"welcome_bonus_active"` WelcomeBonusMultiplier float32 `json:"welcome_bonus_multiplier"` WelcomeBonusCap float32 `json:"welcome_bonus_cap"` WelcomeBonusCount int64 `json:"welcome_bonus_count"` WelcomeBonusExpire int64 `json:"welcome_bonus_expiry"` } func ConvertSettingListRes(settings SettingList) SettingListRes { return SettingListRes{ SMSProvider: settings.SMSProvider, MaxNumberOfOutcomes: settings.MaxNumberOfOutcomes, BetAmountLimit: settings.BetAmountLimit.Float32(), DailyTicketPerIP: settings.DailyTicketPerIP, TotalWinningLimit: settings.TotalWinningLimit.Float32(), AmountForBetReferral: settings.AmountForBetReferral.Float32(), CashbackAmountCap: settings.CashbackAmountCap.Float32(), DefaultWinningLimit: settings.DefaultWinningLimit, ReferralRewardAmount: settings.ReferralRewardAmount.Float32(), CashbackPercentage: settings.CashbackPercentage, DefaultMaxReferrals: settings.DefaultMaxReferrals, MinimumBetAmount: settings.MinimumBetAmount.Float32(), BetDuplicateLimit: settings.BetDuplicateLimit, SendEmailOnBetFinish: settings.SendEmailOnBetFinish, SendSMSOnBetFinish: settings.SendSMSOnBetFinish, WelcomeBonusActive: settings.WelcomeBonusActive, WelcomeBonusMultiplier: settings.WelcomeBonusMultiplier, WelcomeBonusCap: settings.WelcomeBonusCap.Float32(), WelcomeBonusCount: settings.WelcomeBonusCount, WelcomeBonusExpire: settings.WelcomeBonusExpire, } } type SaveSettingListReq struct { SMSProvider *string `json:"sms_provider,omitempty"` MaxNumberOfOutcomes *int64 `json:"max_number_of_outcomes,omitempty"` BetAmountLimit *float32 `json:"bet_amount_limit,omitempty"` DailyTicketPerIP *int64 `json:"daily_ticket_limit,omitempty"` TotalWinningLimit *float32 `json:"total_winning_limit,omitempty"` AmountForBetReferral *float32 `json:"amount_for_bet_referral,omitempty"` CashbackAmountCap *float32 `json:"cashback_amount_cap,omitempty"` DefaultWinningLimit *int64 `json:"default_winning_limit,omitempty"` ReferralRewardAmount *float32 `json:"referral_reward_amount"` CashbackPercentage *float32 `json:"cashback_percentage"` DefaultMaxReferrals *int64 `json:"default_max_referrals"` MinimumBetAmount *float32 `json:"minimum_bet_amount"` BetDuplicateLimit *int64 `json:"bet_duplicate_limit"` SendEmailOnBetFinish *bool `json:"send_email_on_bet_finish"` SendSMSOnBetFinish *bool `json:"send_sms_on_bet_finish"` WelcomeBonusActive *bool `json:"welcome_bonus_active"` WelcomeBonusMultiplier *float32 `json:"welcome_bonus_multiplier"` WelcomeBonusCap *float32 `json:"welcome_bonus_cap"` WelcomeBonusCount *int64 `json:"welcome_bonus_count"` WelcomeBonusExpire *int64 `json:"welcome_bonus_expiry"` } type ValidSettingList struct { SMSProvider ValidString MaxNumberOfOutcomes ValidInt64 BetAmountLimit ValidCurrency DailyTicketPerIP ValidInt64 TotalWinningLimit ValidCurrency AmountForBetReferral ValidCurrency CashbackAmountCap ValidCurrency DefaultWinningLimit ValidInt64 ReferralRewardAmount ValidCurrency CashbackPercentage ValidFloat32 DefaultMaxReferrals ValidInt64 MinimumBetAmount ValidCurrency BetDuplicateLimit ValidInt64 SendEmailOnBetFinish ValidBool SendSMSOnBetFinish ValidBool WelcomeBonusActive ValidBool WelcomeBonusMultiplier ValidFloat32 WelcomeBonusCap ValidCurrency WelcomeBonusCount ValidInt64 WelcomeBonusExpire ValidInt64 } func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList { return ValidSettingList{ SMSProvider: ConvertStringPtr(settings.SMSProvider), MaxNumberOfOutcomes: ConvertInt64Ptr(settings.MaxNumberOfOutcomes), BetAmountLimit: ConvertFloat32PtrToCurrency(settings.BetAmountLimit), DailyTicketPerIP: ConvertInt64Ptr(settings.DailyTicketPerIP), TotalWinningLimit: ConvertFloat32PtrToCurrency(settings.TotalWinningLimit), AmountForBetReferral: ConvertFloat32PtrToCurrency(settings.AmountForBetReferral), CashbackAmountCap: ConvertFloat32PtrToCurrency(settings.CashbackAmountCap), DefaultWinningLimit: ConvertInt64Ptr(settings.DefaultWinningLimit), ReferralRewardAmount: ConvertFloat32PtrToCurrency(settings.ReferralRewardAmount), CashbackPercentage: ConvertFloat32Ptr(settings.CashbackPercentage), DefaultMaxReferrals: ConvertInt64Ptr(settings.DefaultMaxReferrals), MinimumBetAmount: ConvertFloat32PtrToCurrency(settings.MinimumBetAmount), BetDuplicateLimit: ConvertInt64Ptr(settings.BetDuplicateLimit), SendEmailOnBetFinish: ConvertBoolPtr(settings.SendEmailOnBetFinish), SendSMSOnBetFinish: ConvertBoolPtr(settings.SendSMSOnBetFinish), WelcomeBonusActive: ConvertBoolPtr(settings.WelcomeBonusActive), WelcomeBonusMultiplier: ConvertFloat32Ptr(settings.WelcomeBonusMultiplier), WelcomeBonusCap: ConvertFloat32PtrToCurrency(settings.WelcomeBonusCap), WelcomeBonusCount: ConvertInt64Ptr(settings.WelcomeBonusCount), WelcomeBonusExpire: ConvertInt64Ptr(settings.WelcomeBonusExpire), } } // Always make sure to run the validation before converting this func (vsl *ValidSettingList) ToSettingList() SettingList { return SettingList{ SMSProvider: SMSProvider(vsl.SMSProvider.Value), MaxNumberOfOutcomes: vsl.MaxNumberOfOutcomes.Value, BetAmountLimit: vsl.BetAmountLimit.Value, DailyTicketPerIP: vsl.DailyTicketPerIP.Value, TotalWinningLimit: vsl.TotalWinningLimit.Value, AmountForBetReferral: vsl.AmountForBetReferral.Value, CashbackAmountCap: vsl.CashbackAmountCap.Value, DefaultWinningLimit: vsl.DefaultWinningLimit.Value, ReferralRewardAmount: vsl.ReferralRewardAmount.Value, CashbackPercentage: vsl.CashbackPercentage.Value, DefaultMaxReferrals: vsl.DefaultMaxReferrals.Value, MinimumBetAmount: vsl.MinimumBetAmount.Value, BetDuplicateLimit: vsl.BetDuplicateLimit.Value, SendEmailOnBetFinish: vsl.SendEmailOnBetFinish.Value, SendSMSOnBetFinish: vsl.SendSMSOnBetFinish.Value, WelcomeBonusActive: vsl.WelcomeBonusActive.Value, WelcomeBonusMultiplier: vsl.WelcomeBonusMultiplier.Value, WelcomeBonusCap: vsl.WelcomeBonusCap.Value, WelcomeBonusCount: vsl.WelcomeBonusCount.Value, WelcomeBonusExpire: vsl.WelcomeBonusExpire.Value, } } // Custom Validations for non-generic types func (vsl *ValidSettingList) CustomValidationSettings() error { if !SMSProvider(vsl.SMSProvider.Value).IsValid() { return fmt.Errorf("sms provider invalid") } return nil } func (vsl *ValidSettingList) GetInt64SettingsMap() map[string]*ValidInt64 { return map[string]*ValidInt64{ "max_number_of_outcomes": &vsl.MaxNumberOfOutcomes, "daily_ticket_limit": &vsl.DailyTicketPerIP, "default_winning_limit": &vsl.DefaultWinningLimit, "default_max_referrals": &vsl.DefaultMaxReferrals, "bet_duplicate_limit": &vsl.BetDuplicateLimit, "welcome_bonus_count": &vsl.WelcomeBonusCount, "welcome_bonus_expiry": &vsl.WelcomeBonusExpire, } } func (vsl *ValidSettingList) GetCurrencySettingsMap() map[string]*ValidCurrency { return map[string]*ValidCurrency{ "bet_amount_limit": &vsl.BetAmountLimit, "total_winnings_limit": &vsl.TotalWinningLimit, "amount_for_bet_referral": &vsl.AmountForBetReferral, "cashback_amount_cap": &vsl.CashbackAmountCap, "referral_reward_amount": &vsl.ReferralRewardAmount, "minimum_bet_amount": &vsl.MinimumBetAmount, "welcome_bonus_cap": &vsl.WelcomeBonusCap, } } func (vsl *ValidSettingList) GetStringSettingsMap() map[string]*ValidString { return map[string]*ValidString{ "sms_provider": &vsl.SMSProvider, } } func (vsl *ValidSettingList) GetBoolSettingsMap() map[string]*ValidBool { return map[string]*ValidBool{ "send_email_on_bet_finish": &vsl.SendEmailOnBetFinish, "send_sms_on_bet_finish": &vsl.SendSMSOnBetFinish, "welcome_bonus_active": &vsl.WelcomeBonusActive, } } func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 { return map[string]*ValidFloat32{ "cashback_percentage": &vsl.CashbackPercentage, "welcome_bonus_multiplier": &vsl.WelcomeBonusMultiplier, } } func (vsl *ValidSettingList) GetTimeSettingsMap() map[string]*ValidTime { return map[string]*ValidTime{} } // Setting Functions func (vsl *ValidSettingList) GetTotalSettings() int { return len(vsl.GetInt64SettingsMap()) + len(vsl.GetCurrencySettingsMap()) + len(vsl.GetStringSettingsMap()) + len(vsl.GetBoolSettingsMap()) + len(vsl.GetFloat32SettingsMap()) + len(vsl.GetTimeSettingsMap()) } func (vsl *ValidSettingList) GetAllValid() map[string]*bool { settingValid := make(map[string]*bool) for key, setting := range vsl.GetInt64SettingsMap() { settingValid[key] = &(*setting).Valid } for key, setting := range vsl.GetCurrencySettingsMap() { settingValid[key] = &(*setting).Valid } for key, setting := range vsl.GetStringSettingsMap() { settingValid[key] = &(*setting).Valid } for key, setting := range vsl.GetBoolSettingsMap() { settingValid[key] = &(*setting).Valid } for key, setting := range vsl.GetFloat32SettingsMap() { settingValid[key] = &(*setting).Valid } for key, setting := range vsl.GetTimeSettingsMap() { settingValid[key] = &(*setting).Valid } return settingValid } // func setValidSetting[T any](settings map[string]*T, searchKey string, searchVal string, setVal func(string) (T, error)) error { // for key, setting := range settings { // if key == searchKey { // s, err := setVal(searchVal) // if err != nil { // return err // } // *setting = s // } // return nil // } // return ErrSettingNotFound // } func (vsl *ValidSettingList) SetInt64Setting(searchKey string, searchVal string) error { for key, setting := range vsl.GetInt64SettingsMap() { if key == searchKey { value, err := strconv.ParseInt(searchVal, 10, 64) if err != nil { return err } *setting = ValidInt64{Value: value, Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetCurrencySetting(searchKey string, searchVal string) error { for key, setting := range vsl.GetCurrencySettingsMap() { if key == searchKey { value, err := strconv.ParseInt(searchVal, 10, 64) if err != nil { return err } *setting = ValidCurrency{Value: Currency(value), Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetStringSetting(searchKey string, searchVal string) error { for key, setting := range vsl.GetStringSettingsMap() { if key == searchKey { *setting = ValidString{Value: searchVal, Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetBoolSetting(searchKey string, searchVal string) error { for key, setting := range vsl.GetBoolSettingsMap() { if key == searchKey { value, err := strconv.ParseBool(searchVal) if err != nil { return err } *setting = ValidBool{Value: value, Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetFloat32Setting(searchKey string, searchVal string) error { for key, setting := range vsl.GetFloat32SettingsMap() { if key == searchKey { value, err := strconv.ParseFloat(searchVal, 32) if err != nil { return err } *setting = ValidFloat32{Value: float32(value), Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetTimeSetting(searchKey string, searchVal string) error { for key, setting := range vsl.GetTimeSettingsMap() { if key == searchKey { value, err := time.Parse(time.RFC3339, searchVal) if err != nil { return err } *setting = ValidTime{Value: value, Valid: true} return nil } } return ErrSettingNotFound } func (vsl *ValidSettingList) SetSetting(searchKey string, searchVal string) error { setters := []func(string, string) error{ vsl.SetInt64Setting, vsl.SetCurrencySetting, vsl.SetStringSetting, vsl.SetBoolSetting, vsl.SetFloat32Setting, vsl.SetTimeSetting, } for _, setter := range setters { if err := setter(searchKey, searchVal); err != nil { if err == ErrSettingNotFound { // fmt.Printf("setting is not found %v \n", searchKey) continue // not this setter, try the next } return fmt.Errorf("error while processing setting %q: %w \n", searchKey, err) } return nil // successfully set } // If we get here, none of the setters matched return ErrSettingNotFound } func convertValidSettings[T any]( settings map[string]*T, isValid func(*T) bool, toString func(*T) string, ) []Setting { result := make([]Setting, 0, len(settings)) for key, s := range settings { if isValid(s) { result = append(result, Setting{ Key: key, Value: toString(s), }) } } return result } func (vsl *ValidSettingList) ConvertInt64Settings() []Setting { return convertValidSettings( vsl.GetInt64SettingsMap(), func(s *ValidInt64) bool { return s.Valid }, func(s *ValidInt64) string { return strconv.FormatInt(s.Value, 10) }, ) } func (vsl *ValidSettingList) ConvertCurrencySettings() []Setting { return convertValidSettings( vsl.GetCurrencySettingsMap(), func(s *ValidCurrency) bool { return s.Valid }, func(s *ValidCurrency) string { return strconv.FormatInt(int64(s.Value), 10) }, ) } func (vsl *ValidSettingList) ConvertStringSettings() []Setting { return convertValidSettings( vsl.GetStringSettingsMap(), func(s *ValidString) bool { return s.Valid }, func(s *ValidString) string { return s.Value }, ) } func (vsl *ValidSettingList) ConvertBoolSettings() []Setting { return convertValidSettings( vsl.GetBoolSettingsMap(), func(s *ValidBool) bool { return s.Valid }, func(s *ValidBool) string { return strconv.FormatBool(s.Value) }, ) } func (vsl *ValidSettingList) ConvertFloat32Settings() []Setting { return convertValidSettings( vsl.GetFloat32SettingsMap(), func(s *ValidFloat32) bool { return s.Valid }, func(s *ValidFloat32) string { return strconv.FormatFloat(float64(s.Value), 'f', -1, 32) }, ) } func (vsl *ValidSettingList) ConvertTimeSettings() []Setting { return convertValidSettings( vsl.GetTimeSettingsMap(), func(s *ValidTime) bool { return s.Valid }, func(s *ValidTime) string { return s.Value.Format(time.RFC3339) }, ) } func validateSettings[T any]( settings map[string]*T, customValidator func(*T) bool, ) error { var errs []string for key, s := range settings { if !customValidator(s) { errs = append(errs, fmt.Sprintf("%v is invalid", key)) } } if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } func (vsl *ValidSettingList) ValidateInt64Settings() error { return validateSettings(vsl.GetInt64SettingsMap(), func(s *ValidInt64) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateCurrencySettings() error { return validateSettings(vsl.GetCurrencySettingsMap(), func(s *ValidCurrency) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateStringSettings() error { return validateSettings(vsl.GetStringSettingsMap(), func(s *ValidString) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateBoolSettings() error { return validateSettings(vsl.GetBoolSettingsMap(), func(s *ValidBool) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateFloat32Settings() error { return validateSettings(vsl.GetFloat32SettingsMap(), func(s *ValidFloat32) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateTimeSettings() error { return validateSettings(vsl.GetTimeSettingsMap(), func(s *ValidTime) bool { return s.Valid }, ) } func (vsl *ValidSettingList) ValidateAllSettings() error { var errs []string validators := []func() error{ vsl.ValidateInt64Settings, vsl.ValidateCurrencySettings, vsl.ValidateStringSettings, vsl.ValidateBoolSettings, vsl.ValidateFloat32Settings, vsl.ValidateTimeSettings, vsl.CustomValidationSettings, } for _, validator := range validators { if err := validator(); err != nil { errs = append(errs, err.Error()) } } if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } func (vsl *ValidSettingList) ConvertAllSettings() []Setting { totalCap := vsl.GetTotalSettings() all := make([]Setting, 0, totalCap) all = append(all, vsl.ConvertInt64Settings()...) all = append(all, vsl.ConvertCurrencySettings()...) all = append(all, vsl.ConvertStringSettings()...) all = append(all, vsl.ConvertBoolSettings()...) all = append(all, vsl.ConvertFloat32Settings()...) all = append(all, vsl.ConvertTimeSettings()...) return all } func ConvertDBGlobalSettingList(settings []dbgen.GlobalSetting) (SettingList, error) { var dbSettingList ValidSettingList for _, setting := range settings { if err := dbSettingList.SetSetting(setting.Key, setting.Value); err != nil { if err == ErrSettingNotFound { MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) } MongoDBLogger.Error("unknown error while fetching settings", zap.Error(err)) } } if err := dbSettingList.ValidateAllSettings(); err != nil { MongoDBLogger.Warn("setting validation error", zap.Error(err), zap.Any("db_setting_list", dbSettingList)) return SettingList{}, err } settingList := dbSettingList.ToSettingList() return settingList, nil } func ConvertDBOverrideSettingList(settings []dbgen.GetOverrideSettingsRow) (SettingList, error) { var dbSettingList ValidSettingList for _, setting := range settings { if err := dbSettingList.SetSetting(setting.Key, setting.Value); err != nil { if err == ErrSettingNotFound { MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) } } } if err := dbSettingList.ValidateAllSettings(); err != nil { MongoDBLogger.Warn("setting validation error", zap.Error(err)) return SettingList{}, err } settingList := dbSettingList.ToSettingList() return settingList, nil }