feat: finished the setting service

This commit is contained in:
Samuel Tariku 2025-06-21 17:44:34 +03:00
parent 050fe16f54
commit 9ec7d0cfc1
13 changed files with 165 additions and 19 deletions

View File

@ -43,6 +43,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -98,6 +99,7 @@ func main() {
v := customvalidator.NewCustomValidator(validator.New())
// Initialize services
settingSvc := settings.NewService(store)
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
userSvc := user.NewService(store, store, cfg)
eventSvc := event.New(cfg.Bet365Token, store)
@ -119,7 +121,7 @@ func main() {
branchSvc := branch.NewService(store)
companySvc := company.NewService(store)
leagueSvc := league.New(store)
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger)
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc)
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
referalRepo := repository.NewReferralRepository(store)
@ -202,6 +204,7 @@ func main() {
currSvc,
cfg.Port,
v,
settingSvc,
authSvc,
logger,
jwtutil.JwtConfig{

View File

@ -1,5 +1,17 @@
-- Settings Initial Data
INSERT INTO settings (key, value)
VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('bet_amount_limit', '100000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('daily_ticket_limit', '50') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;

View File

@ -1,6 +1,10 @@
-- name: GetSettings :many
SELECT *
from settings;
FROM settings;
-- name: GetSetting :one
SELECT *
FROM settings
WHERE key = $1;
-- name: SaveSetting :one
INSERT INTO settings (key, value, updated_at)
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO

View File

@ -9,9 +9,27 @@ import (
"context"
)
const GetSetting = `-- name: GetSetting :one
SELECT key, value, created_at, updated_at
FROM settings
WHERE key = $1
`
func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) {
row := q.db.QueryRow(ctx, GetSetting, key)
var i Setting
err := row.Scan(
&i.Key,
&i.Value,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetSettings = `-- name: GetSettings :many
SELECT key, value, created_at, updated_at
from settings
FROM settings
`
func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) {

View File

@ -13,3 +13,10 @@ type SettingRes struct {
Value string `json:"value"`
UpdatedAt string `json:"updated_at"`
}
type SettingList struct {
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"`
}

View File

@ -2,12 +2,68 @@ package repository
import (
"context"
"strconv"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"go.uber.org/zap"
)
type DBSettingList struct {
MaxNumberOfOutcomes domain.ValidInt64
BetAmountLimit domain.ValidInt64
DailyTicketPerIP domain.ValidInt64
TotalWinningLimit domain.ValidInt64
}
func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
var dbSettingList DBSettingList
var int64SettingsMap = map[string]*domain.ValidInt64{
"max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes,
"bet_amount_limit": &dbSettingList.BetAmountLimit,
"daily_ticket_limit": &dbSettingList.DailyTicketPerIP,
"total_winnings_limit": &dbSettingList.DailyTicketPerIP,
}
for _, setting := range settings {
for key, dbSetting := range int64SettingsMap {
if setting.Key == key {
value, err := strconv.ParseInt(setting.Value, 10, 64)
if err != nil {
return domain.SettingList{}, err
}
*dbSetting = domain.ValidInt64{
Value: value,
Valid: true,
}
} else {
domain.MongoDBLogger.Error("unknown setting found on database", zap.String("setting", setting.Key))
}
}
}
for key, dbSetting := range int64SettingsMap {
if !dbSetting.Valid {
domain.MongoDBLogger.Warn("setting value not found on database", zap.String("setting", key))
}
}
return domain.SettingList{
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
BetAmountLimit: domain.Currency(dbSettingList.BetAmountLimit.Value),
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
TotalWinningLimit: domain.Currency(dbSettingList.TotalWinningLimit.Value),
}, nil
}
func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) {
settings, err := s.queries.GetSettings(ctx)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
return GetDBSettingList(settings)
}
func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
settings, err := s.queries.GetSettings(ctx)
@ -27,9 +83,25 @@ func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
return result, nil
}
func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, error) {
dbSetting, err := s.queries.GetSetting(ctx, key)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
result := domain.Setting{
Key: dbSetting.Key,
Value: dbSetting.Value,
UpdatedAt: dbSetting.UpdatedAt.Time,
}
return result, nil
}
func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{
Key: key,
Key: key,
Value: value,
})
@ -40,7 +112,7 @@ func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Sett
}
setting := domain.Setting{
Key: dbSetting.Key,
Key: dbSetting.Key,
Value: dbSetting.Value,
}

View File

@ -7,6 +7,8 @@ import (
)
type SettingStore interface {
GetSettingList(ctx context.Context) (domain.SettingList, error)
GetSettings(ctx context.Context) ([]domain.Setting, error)
GetSetting(ctx context.Context, key string) (domain.Setting, error)
SaveSetting(ctx context.Context, key, value string) (domain.Setting, error)
}

View File

@ -16,10 +16,17 @@ func NewService(settingStore SettingStore) *Service {
}
}
func (s *Service) GetSettingList(ctx context.Context) (domain.SettingList, error) {
return s.settingStore.GetSettingList(ctx)
}
func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) {
return s.settingStore.GetSettings(ctx)
}
func (s *Service) GetSetting(ctx context.Context, key string) (domain.Setting, error) {
return s.settingStore.GetSetting(ctx, key)
}
func (s *Service) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
return s.settingStore.SaveSetting(ctx, key, value)
}

View File

@ -10,13 +10,14 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"go.uber.org/zap"
)
var (
// ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
// ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
ErrEventHasNotEnded = errors.New("Event has not ended yet")
ErrTicketHasExpired = errors.New("Ticket has expired")
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
ErrEventHasBeenRemoved = errors.New("Event has been removed")
ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket")
@ -32,6 +33,7 @@ type Service struct {
eventSvc event.Service
prematchSvc odds.ServiceImpl
mongoLogger *zap.Logger
settingSvc settings.Service
}
func NewService(
@ -39,16 +41,18 @@ func NewService(
eventSvc event.Service,
prematchSvc odds.ServiceImpl,
mongoLogger *zap.Logger,
settingSvc settings.Service,
) *Service {
return &Service{
ticketStore: ticketStore,
eventSvc: eventSvc,
prematchSvc: prematchSvc,
mongoLogger: mongoLogger,
settingSvc: settingSvc,
}
}
func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.SettingList, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
eventIDStr := strconv.FormatInt(eventID, 10)
marketIDStr := strconv.FormatInt(marketID, 10)
oddIDStr := strconv.FormatInt(oddID, 10)
@ -69,7 +73,7 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, mark
zap.Time("event_start_time", event.StartTime),
zap.Time("current_time", currentTime),
)
return domain.CreateTicketOutcome{}, ErrEventHasNotEnded
return domain.CreateTicketOutcome{}, ErrTicketHasExpired
}
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
@ -151,17 +155,18 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, mark
}
func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) {
settingsList, err := s.settingSvc.GetSettingList(ctx)
// s.mongoLogger.Info("Creating ticket")
// TODO Validate Outcomes Here and make sure they didn't expire
// Validation for creating tickets
if len(req.Outcomes) > 30 {
if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket
}
if req.Amount > 100000 {
if req.Amount > settingsList.BetAmountLimit.Float32() {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
return domain.Ticket{}, 0, ErrTicketAmountTooHigh
}
@ -170,17 +175,20 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
if err != nil {
// return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
s.mongoLogger.Error("failed to count number of ticket using ip",
zap.Error(err),
)
return domain.Ticket{}, 0, err
}
if count > 50 {
if count > settingsList.DailyTicketPerIP {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
}
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
var totalOdds float32 = 1
for _, outcomeReq := range req.Outcomes {
newOutcome, err := s.GenerateTicketOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
newOutcome, err := s.GenerateTicketOutcome(ctx, settingsList, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
if err != nil {
s.mongoLogger.Error("failed to generate outcome",
zap.Int64("event_id", outcomeReq.EventID),
@ -194,7 +202,7 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
outcomes = append(outcomes, newOutcome)
}
totalWinnings := req.Amount * totalOdds
if totalWinnings > 1000000 {
if totalWinnings > settingsList.TotalWinningLimit.Float32() {
s.mongoLogger.Error("Total Winnings over limit", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
return domain.Ticket{}, 0, ErrTicketWinningTooHigh

View File

@ -18,6 +18,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -45,6 +46,7 @@ type App struct {
NotidicationStore *notificationservice.Service
referralSvc referralservice.ReferralStore
port int
settingSvc *settings.Service
authSvc *authentication.Service
userSvc *user.Service
betSvc *bet.Service
@ -68,6 +70,7 @@ type App struct {
func NewApp(
currSvc *currency.Service,
port int, validator *customvalidator.CustomValidator,
settingSvc *settings.Service,
authSvc *authentication.Service,
logger *slog.Logger,
JwtConfig jwtutil.JwtConfig,
@ -107,9 +110,11 @@ func NewApp(
}))
s := &App{
currSvc: currSvc,
fiber: app,
port: port,
currSvc: currSvc,
fiber: app,
port: port,
settingSvc: settingSvc,
authSvc: authSvc,
validator: validator,
logger: logger,

View File

@ -18,6 +18,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -31,6 +32,7 @@ import (
type Handler struct {
currSvc *currency.Service
logger *slog.Logger
settingSvc *settings.Service
notificationSvc *notificationservice.Service
userSvc *user.Service
referralSvc referralservice.ReferralStore
@ -59,6 +61,7 @@ type Handler struct {
func New(
currSvc *currency.Service,
logger *slog.Logger,
settingSvc *settings.Service,
notificationSvc *notificationservice.Service,
validator *customvalidator.CustomValidator,
reportSvc report.ReportStore,
@ -86,6 +89,7 @@ func New(
return &Handler{
currSvc: currSvc,
logger: logger,
settingSvc: settingSvc,
notificationSvc: notificationSvc,
reportSvc: reportSvc,
chapaSvc: chapaSvc,

View File

@ -35,7 +35,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
if err != nil {
switch err {
case ticket.ErrEventHasBeenRemoved, ticket.ErrEventHasNotEnded, ticket.ErrRawOddInvalid:
case ticket.ErrEventHasBeenRemoved, ticket.ErrTicketHasExpired,
ticket.ErrRawOddInvalid, ticket.ErrTooManyOutcomesForTicket,
ticket.ErrTicketAmountTooHigh, ticket.ErrTicketLimitForSingleUser,
ticket.ErrTicketWinningTooHigh:
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return fiber.NewError(fiber.StatusInternalServerError, err.Error())

View File

@ -22,6 +22,7 @@ func (a *App) initAppRoutes() {
h := handlers.New(
a.currSvc,
a.logger,
a.settingSvc,
a.NotidicationStore,
a.validator,
a.reportSvc,