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"` } 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"` } 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(), } } 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"` } 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), } } type ValidSettingList struct { SMSProvider ValidString MaxNumberOfOutcomes ValidInt64 BetAmountLimit ValidCurrency DailyTicketPerIP ValidInt64 TotalWinningLimit ValidCurrency AmountForBetReferral ValidCurrency CashbackAmountCap ValidCurrency } // 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: Currency(vsl.BetAmountLimit.Value), DailyTicketPerIP: vsl.DailyTicketPerIP.Value, TotalWinningLimit: Currency(vsl.TotalWinningLimit.Value), AmountForBetReferral: Currency(vsl.AmountForBetReferral.Value), CashbackAmountCap: Currency(vsl.CashbackAmountCap.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, } } 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, } } 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{} } func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 { return map[string]*ValidFloat32{} } func (vsl *ValidSettingList) GetTimeSettingsMap() map[string]*ValidTime { return map[string]*ValidTime{} } 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 }