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"` } 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, } } 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"` } 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 } 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), } } // 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, } } // 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, } } 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, } } 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, } } func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 { return map[string]*ValidFloat32{ "cashback_percentage": &vsl.CashbackPercentage, } } 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 }