mongoLogger to bet service

This commit is contained in:
Yared Yemane 2025-06-09 13:14:59 +03:00
parent 6dbce0725d
commit 788e3ee9a6
7 changed files with 507 additions and 141 deletions

View File

@ -10,10 +10,13 @@ import (
"time" "time"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"go.uber.org/zap"
// "github.com/gofiber/fiber/v2" // "github.com/gofiber/fiber/v2"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/config"
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger" customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
"github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
// mongologger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger" // mongologger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
@ -80,7 +83,8 @@ func main() {
logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel) logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel)
// mongologger.Init() mongoLogger.Init()
mongoDBLogger := zap.L()
// client := mongoLogger.InitDB() // client := mongoLogger.InitDB()
// defer func() { // defer func() {
@ -139,7 +143,7 @@ func main() {
branchSvc := branch.NewService(store) branchSvc := branch.NewService(store)
companySvc := company.NewService(store) companySvc := company.NewService(store)
leagueSvc := league.New(store) leagueSvc := league.New(store)
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger) betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger, mongoDBLogger)
resultSvc := result.NewService(store, cfg, logger, *betSvc) resultSvc := result.NewService(store, cfg, logger, *betSvc)
referalRepo := repository.NewReferralRepository(store) referalRepo := repository.NewReferralRepository(store)
vitualGameRepo := repository.NewVirtualGameRepository(store) vitualGameRepo := repository.NewVirtualGameRepository(store)

View File

@ -25,6 +25,6 @@ func Init() {
defer logger.Sync() defer logger.Sync()
logger.Info("Application started", zap.String("module", "main")) // logger.Info("Application started", zap.String("module", "main"))
logger.Error("Something went wrong", zap.String("error_code", "E123")) // logger.Error("Something went wrong", zap.String("error_code", "E123"))
} }

View File

@ -11,9 +11,13 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"go.uber.org/zap"
) )
var logger *slog.Logger var (
logger *slog.Logger
mongoLogger *zap.Logger
)
func convertDBBet(bet dbgen.Bet) domain.Bet { func convertDBBet(bet dbgen.Bet) domain.Bet {
return domain.Bet{ return domain.Bet{
@ -151,9 +155,14 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
for _, outcome := range outcomes { for _, outcome := range outcomes {
dbParams = append(dbParams, convertDBCreateBetOutcome(outcome)) dbParams = append(dbParams, convertDBCreateBetOutcome(outcome))
} }
rows, err := s.queries.CreateBetOutcome(ctx, dbParams)
rows, err := s.queries.CreateBetOutcome(ctx, dbParams)
if err != nil { if err != nil {
mongoLogger.Error("failed to create bet outcomes in DB",
zap.Int("outcome_count", len(outcomes)),
zap.Any("bet_id", outcomes[0].BetID), // assumes all outcomes have same BetID
zap.Error(err),
)
return rows, err return rows, err
} }
@ -162,8 +171,11 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
bet, err := s.queries.GetBetByID(ctx, id) bet, err := s.queries.GetBetByID(ctx, id)
if err != nil { if err != nil {
mongoLogger.Error("failed to get bet by ID",
zap.Int64("bet_id", id),
zap.Error(err),
)
return domain.GetBet{}, err return domain.GetBet{}, err
} }
@ -172,8 +184,11 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) { func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
bet, err := s.queries.GetBetByCashoutID(ctx, id) bet, err := s.queries.GetBetByCashoutID(ctx, id)
if err != nil { if err != nil {
mongoLogger.Error("failed to get bet by cashout ID",
zap.String("cashout_id", id),
zap.Error(err),
)
return domain.GetBet{}, err return domain.GetBet{}, err
} }
@ -196,6 +211,10 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
}, },
}) })
if err != nil { if err != nil {
mongoLogger.Error("failed to get all bets",
zap.Any("filter", filter),
zap.Error(err),
)
return nil, err return nil, err
} }
@ -212,8 +231,11 @@ func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.
Int64: BranchID, Int64: BranchID,
Valid: true, Valid: true,
}) })
if err != nil { if err != nil {
mongoLogger.Error("failed to get bets by branch ID",
zap.Int64("branch_id", BranchID),
zap.Error(err),
)
return nil, err return nil, err
} }
@ -248,6 +270,13 @@ func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) err
ID: id, ID: id,
CashedOut: cashedOut, CashedOut: cashedOut,
}) })
if err != nil {
mongoLogger.Error("failed to update cashout",
zap.Int64("id", id),
zap.Bool("cashed_out", cashedOut),
zap.Error(err),
)
}
return err return err
} }
@ -256,16 +285,27 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
ID: id, ID: id,
Status: int32(status), Status: int32(status),
}) })
if err != nil {
mongoLogger.Error("failed to update status",
zap.Int64("id", id),
zap.Int32("status", int32(status)),
zap.Error(err),
)
}
return err return err
} }
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) { func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID) outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
if err != nil { if err != nil {
return nil, nil mongoLogger.Error("failed to get bet outcomes by event ID",
zap.Int64("event_id", eventID),
zap.Error(err),
)
return nil, err
} }
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
for _, outcome := range outcomes { for _, outcome := range outcomes {
result = append(result, convertDBBetOutcomes(outcome)) result = append(result, convertDBBetOutcomes(outcome))
} }
@ -275,22 +315,36 @@ func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]do
func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) { func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID) outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID)
if err != nil { if err != nil {
return nil, nil mongoLogger.Error("failed to get bet outcomes by bet ID",
zap.Int64("bet_id", betID),
zap.Error(err),
)
return nil, err
} }
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
for _, outcome := range outcomes { for _, outcome := range outcomes {
result = append(result, convertDBBetOutcomes(outcome)) result = append(result, convertDBBetOutcomes(outcome))
} }
return result, nil return result, nil
} }
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) { func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{ update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
Status: int32(status), Status: int32(status),
ID: id, ID: id,
}) })
if err != nil {
mongoLogger.Error("failed to update bet outcome status",
zap.Int64("id", id),
zap.Int32("status", int32(status)),
zap.Error(err),
)
return domain.BetOutcome{}, err
}
res := convertDBBetOutcomes(update) res := convertDBBetOutcomes(update)
return res, err return res, nil
} }
func (s *Store) DeleteBet(ctx context.Context, id int64) error { func (s *Store) DeleteBet(ctx context.Context, id int64) error {
@ -374,9 +428,24 @@ func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
row := s.conn.QueryRow(ctx, query, args...) row := s.conn.QueryRow(ctx, query, args...)
err = row.Scan(&totalStakes, &totalBets, &activeBets, &totalWins, &totalLosses, &winBalance) err = row.Scan(&totalStakes, &totalBets, &activeBets, &totalWins, &totalLosses, &winBalance)
if err != nil { if err != nil {
mongoLogger.Error("failed to get bet summary",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return 0, 0, 0, 0, 0, 0, fmt.Errorf("failed to get bet summary: %w", err) return 0, 0, 0, 0, 0, 0, fmt.Errorf("failed to get bet summary: %w", err)
} }
mongoLogger.Info("GetBetSummary executed successfully",
zap.String("query", query),
zap.Any("args", args),
zap.Float64("totalStakes", float64(totalStakes)), // convert if needed
zap.Int64("totalBets", totalBets),
zap.Int64("activeBets", activeBets),
zap.Int64("totalWins", totalWins),
zap.Int64("totalLosses", totalLosses),
zap.Float64("winBalance", float64(winBalance)), // convert if needed
)
return totalStakes, totalBets, activeBets, totalWins, totalLosses, winBalance, nil return totalStakes, totalBets, activeBets, totalWins, totalLosses, winBalance, nil
} }
@ -450,6 +519,11 @@ func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query bet stats",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query bet stats: %w", err) return nil, fmt.Errorf("failed to query bet stats: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -465,15 +539,26 @@ func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]
&stat.TotalPayouts, &stat.TotalPayouts,
&stat.AverageOdds, &stat.AverageOdds,
); err != nil { ); err != nil {
mongoLogger.Error("failed to scan bet stat",
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan bet stat: %w", err) return nil, fmt.Errorf("failed to scan bet stat: %w", err)
} }
stats = append(stats, stat) stats = append(stats, stat)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration",
zap.Error(err),
)
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetBetStats executed successfully",
zap.Int("result_count", len(stats)),
zap.String("query", query),
zap.Any("args", args),
)
return stats, nil return stats, nil
} }
@ -530,6 +615,11 @@ func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilt
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query sport popularity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query sport popularity: %w", err) return nil, fmt.Errorf("failed to query sport popularity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -539,15 +629,26 @@ func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilt
var date time.Time var date time.Time
var sportID string var sportID string
if err := rows.Scan(&date, &sportID); err != nil { if err := rows.Scan(&date, &sportID); err != nil {
mongoLogger.Error("failed to scan sport popularity",
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan sport popularity: %w", err) return nil, fmt.Errorf("failed to scan sport popularity: %w", err)
} }
popularity[date] = sportID popularity[date] = sportID
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration",
zap.Error(err),
)
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetSportPopularity executed successfully",
zap.Int("result_count", len(popularity)),
zap.String("query", query),
zap.Any("args", args),
)
return popularity, nil return popularity, nil
} }
@ -604,6 +705,11 @@ func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFil
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query market popularity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query market popularity: %w", err) return nil, fmt.Errorf("failed to query market popularity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -613,15 +719,26 @@ func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFil
var date time.Time var date time.Time
var marketName string var marketName string
if err := rows.Scan(&date, &marketName); err != nil { if err := rows.Scan(&date, &marketName); err != nil {
mongoLogger.Error("failed to scan market popularity",
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan market popularity: %w", err) return nil, fmt.Errorf("failed to scan market popularity: %w", err)
} }
popularity[date] = marketName popularity[date] = marketName
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration",
zap.Error(err),
)
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetMarketPopularity executed successfully",
zap.Int("result_count", len(popularity)),
zap.String("query", query),
zap.Any("args", args),
)
return popularity, nil return popularity, nil
} }
@ -692,6 +809,11 @@ func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query extreme values",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query extreme values: %w", err) return nil, fmt.Errorf("failed to query extreme values: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -701,15 +823,26 @@ func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter
var date time.Time var date time.Time
var extreme domain.ExtremeValues var extreme domain.ExtremeValues
if err := rows.Scan(&date, &extreme.HighestStake, &extreme.HighestPayout); err != nil { if err := rows.Scan(&date, &extreme.HighestStake, &extreme.HighestPayout); err != nil {
mongoLogger.Error("failed to scan extreme values",
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan extreme values: %w", err) return nil, fmt.Errorf("failed to scan extreme values: %w", err)
} }
extremes[date] = extreme extremes[date] = extreme
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration",
zap.Error(err),
)
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetExtremeValues executed successfully",
zap.Int("result_count", len(extremes)),
zap.String("query", query),
zap.Any("args", args),
)
return extremes, nil return extremes, nil
} }
@ -766,6 +899,11 @@ func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.Report
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query customer bet activity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query customer bet activity: %w", err) return nil, fmt.Errorf("failed to query customer bet activity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -783,15 +921,26 @@ func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.Report
&activity.LastBetDate, &activity.LastBetDate,
&activity.AverageOdds, &activity.AverageOdds,
); err != nil { ); err != nil {
mongoLogger.Error("failed to scan customer bet activity",
zap.Error(err),
)
return nil, fmt.Errorf("failed to scan customer bet activity: %w", err) return nil, fmt.Errorf("failed to scan customer bet activity: %w", err)
} }
activities = append(activities, activity) activities = append(activities, activity)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration",
zap.Error(err),
)
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetCustomerBetActivity executed successfully",
zap.Int("result_count", len(activities)),
zap.String("query", query),
zap.Any("args", args),
)
return activities, nil return activities, nil
} }
@ -840,6 +989,11 @@ func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFi
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query branch bet activity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query branch bet activity: %w", err) return nil, fmt.Errorf("failed to query branch bet activity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -854,15 +1008,22 @@ func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFi
&activity.TotalWins, &activity.TotalWins,
&activity.TotalPayouts, &activity.TotalPayouts,
); err != nil { ); err != nil {
mongoLogger.Error("failed to scan branch bet activity", zap.Error(err))
return nil, fmt.Errorf("failed to scan branch bet activity: %w", err) return nil, fmt.Errorf("failed to scan branch bet activity: %w", err)
} }
activities = append(activities, activity) activities = append(activities, activity)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration", zap.Error(err))
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetBranchBetActivity executed successfully",
zap.Int("result_count", len(activities)),
zap.String("query", query),
zap.Any("args", args),
)
return activities, nil return activities, nil
} }
@ -882,7 +1043,6 @@ func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFil
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
// Add filters if provided
if filter.CompanyID.Valid { if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) args = append(args, filter.CompanyID.Value)
@ -918,6 +1078,11 @@ func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFil
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query sport bet activity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query sport bet activity: %w", err) return nil, fmt.Errorf("failed to query sport bet activity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -933,15 +1098,22 @@ func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFil
&activity.TotalPayouts, &activity.TotalPayouts,
&activity.AverageOdds, &activity.AverageOdds,
); err != nil { ); err != nil {
mongoLogger.Error("failed to scan sport bet activity", zap.Error(err))
return nil, fmt.Errorf("failed to scan sport bet activity: %w", err) return nil, fmt.Errorf("failed to scan sport bet activity: %w", err)
} }
activities = append(activities, activity) activities = append(activities, activity)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration", zap.Error(err))
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetSportBetActivity executed successfully",
zap.Int("result_count", len(activities)),
zap.String("query", query),
zap.Any("args", args),
)
return activities, nil return activities, nil
} }
@ -950,12 +1122,27 @@ func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter)
query := `SELECT DISTINCT bo.sport_id, e.match_name query := `SELECT DISTINCT bo.sport_id, e.match_name
FROM bet_outcomes bo FROM bet_outcomes bo
JOIN events e ON bo.event_id = e.id::bigint JOIN events e ON bo.event_id = e.id::bigint
JOIN bets b ON b.id = bo.bet_id
WHERE bo.sport_id IS NOT NULL` WHERE bo.sport_id IS NOT NULL`
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
// Add filters if provided if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
argPos++
}
if filter.StartTime.Valid { if filter.StartTime.Valid {
query += fmt.Sprintf(" AND bo.created_at >= $%d", argPos) query += fmt.Sprintf(" AND bo.created_at >= $%d", argPos)
args = append(args, filter.StartTime.Value) args = append(args, filter.StartTime.Value)
@ -969,6 +1156,11 @@ func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter)
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query sport details",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query sport details: %w", err) return nil, fmt.Errorf("failed to query sport details: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -977,15 +1169,23 @@ func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter)
for rows.Next() { for rows.Next() {
var sportID, matchName string var sportID, matchName string
if err := rows.Scan(&sportID, &matchName); err != nil { if err := rows.Scan(&sportID, &matchName); err != nil {
mongoLogger.Error("failed to scan sport detail", zap.Error(err))
return nil, fmt.Errorf("failed to scan sport detail: %w", err) return nil, fmt.Errorf("failed to scan sport detail: %w", err)
} }
details[sportID] = matchName details[sportID] = matchName
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration", zap.Error(err))
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetSportDetails executed successfully",
zap.Int("result_count", len(details)),
zap.String("query", query),
zap.Any("args", args),
)
return details, nil return details, nil
} }
@ -995,7 +1195,7 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
SELECT SELECT
bo.sport_id, bo.sport_id,
bo.market_name, bo.market_name,
COUNT(*) as bet_count, COUNT(*) AS bet_count,
ROW_NUMBER() OVER (PARTITION BY bo.sport_id ORDER BY COUNT(*) DESC) as rank ROW_NUMBER() OVER (PARTITION BY bo.sport_id ORDER BY COUNT(*) DESC) as rank
FROM bets b FROM bets b
JOIN bet_outcomes bo ON b.id = bo.bet_id JOIN bet_outcomes bo ON b.id = bo.bet_id
@ -1004,7 +1204,6 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
// Add filters if provided
if filter.CompanyID.Valid { if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) args = append(args, filter.CompanyID.Value)
@ -1042,6 +1241,11 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
rows, err := s.conn.Query(ctx, query, args...) rows, err := s.conn.Query(ctx, query, args...)
if err != nil { if err != nil {
mongoLogger.Error("failed to query sport market popularity",
zap.String("query", query),
zap.Any("args", args),
zap.Error(err),
)
return nil, fmt.Errorf("failed to query sport market popularity: %w", err) return nil, fmt.Errorf("failed to query sport market popularity: %w", err)
} }
defer rows.Close() defer rows.Close()
@ -1050,14 +1254,22 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
for rows.Next() { for rows.Next() {
var sportID, marketName string var sportID, marketName string
if err := rows.Scan(&sportID, &marketName); err != nil { if err := rows.Scan(&sportID, &marketName); err != nil {
mongoLogger.Error("failed to scan sport market popularity", zap.Error(err))
return nil, fmt.Errorf("failed to scan sport market popularity: %w", err) return nil, fmt.Errorf("failed to scan sport market popularity: %w", err)
} }
popularity[sportID] = marketName popularity[sportID] = marketName
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
mongoLogger.Error("rows error after iteration", zap.Error(err))
return nil, fmt.Errorf("rows error: %w", err) return nil, fmt.Errorf("rows error: %w", err)
} }
mongoLogger.Info("GetSportMarketPopularity executed successfully",
zap.Int("result_count", len(popularity)),
zap.String("query", query),
zap.Any("args", args),
)
return popularity, nil return popularity, nil
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"go.uber.org/zap"
) )
var ( var (
@ -33,9 +34,10 @@ type Service struct {
walletSvc wallet.Service walletSvc wallet.Service
branchSvc branch.Service branchSvc branch.Service
logger *slog.Logger logger *slog.Logger
mongoLogger *zap.Logger
} }
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Service, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger) *Service { func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Service, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service {
return &Service{ return &Service{
betStore: betStore, betStore: betStore,
eventSvc: eventSvc, eventSvc: eventSvc,
@ -43,6 +45,7 @@ func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Serv
walletSvc: walletSvc, walletSvc: walletSvc,
branchSvc: branchSvc, branchSvc: branchSvc,
logger: logger, logger: logger,
mongoLogger: mongoLogger,
} }
} }
@ -58,37 +61,56 @@ func (s *Service) GenerateCashoutID() (string, error) {
const length int = 13 const length int = 13
charLen := big.NewInt(int64(len(chars))) charLen := big.NewInt(int64(len(chars)))
result := make([]byte, length) result := make([]byte, length)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
index, err := rand.Int(rand.Reader, charLen) index, err := rand.Int(rand.Reader, charLen)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate random index for cashout ID",
zap.Int("position", i),
zap.Error(err),
)
return "", err return "", err
} }
result[i] = chars[index.Int64()] result[i] = chars[index.Int64()]
} }
return string(result), nil return string(result), nil
} }
func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) { func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) {
// TODO: Change this when you refactor the database code
eventIDStr := strconv.FormatInt(eventID, 10) eventIDStr := strconv.FormatInt(eventID, 10)
marketIDStr := strconv.FormatInt(marketID, 10) marketIDStr := strconv.FormatInt(marketID, 10)
oddIDStr := strconv.FormatInt(oddID, 10) oddIDStr := strconv.FormatInt(oddID, 10)
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr) event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to fetch upcoming event by ID",
zap.Int64("event_id", eventID),
zap.Error(err),
)
return domain.CreateBetOutcome{}, ErrEventHasBeenRemoved return domain.CreateBetOutcome{}, ErrEventHasBeenRemoved
} }
currentTime := time.Now() currentTime := time.Now()
if event.StartTime.Before(currentTime) { if event.StartTime.Before(currentTime) {
s.mongoLogger.Error("event has already started",
zap.Int64("event_id", eventID),
zap.Time("event_start_time", event.StartTime),
zap.Time("current_time", currentTime),
)
return domain.CreateBetOutcome{}, ErrEventHasNotEnded return domain.CreateBetOutcome{}, ErrEventHasNotEnded
} }
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr) odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get raw odds by market ID",
zap.Int64("event_id", eventID),
zap.Int64("market_id", marketID),
zap.Error(err),
)
return domain.CreateBetOutcome{}, err return domain.CreateBetOutcome{}, err
} }
type rawOddType struct { type rawOddType struct {
ID string ID string
Name string Name string
@ -98,29 +120,51 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
} }
var selectedOdd rawOddType var selectedOdd rawOddType
var isOddFound bool = false var isOddFound bool
for _, raw := range odds.RawOdds { for _, raw := range odds.RawOdds {
var rawOdd rawOddType var rawOdd rawOddType
rawBytes, err := json.Marshal(raw) rawBytes, err := json.Marshal(raw)
if err != nil {
s.mongoLogger.Error("failed to marshal raw odd",
zap.Any("raw", raw),
zap.Error(err),
)
continue
}
err = json.Unmarshal(rawBytes, &rawOdd) err = json.Unmarshal(rawBytes, &rawOdd)
if err != nil { if err != nil {
fmt.Printf("Failed to unmarshal raw odd %v", err) s.mongoLogger.Error("failed to unmarshal raw odd",
zap.ByteString("raw_bytes", rawBytes),
zap.Error(err),
)
continue continue
} }
if rawOdd.ID == oddIDStr { if rawOdd.ID == oddIDStr {
selectedOdd = rawOdd selectedOdd = rawOdd
isOddFound = true isOddFound = true
break
} }
} }
if !isOddFound { if !isOddFound {
s.mongoLogger.Error("odd ID not found in raw odds",
zap.Int64("odd_id", oddID),
zap.Int64("market_id", marketID),
zap.Int64("event_id", eventID),
)
return domain.CreateBetOutcome{}, ErrRawOddInvalid return domain.CreateBetOutcome{}, ErrRawOddInvalid
} }
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to parse selected odd value",
zap.String("odd", selectedOdd.Odds),
zap.Int64("odd_id", oddID),
zap.Error(err),
)
return domain.CreateBetOutcome{}, err return domain.CreateBetOutcome{}, err
} }
newOutcome := domain.CreateBetOutcome{ newOutcome := domain.CreateBetOutcome{
EventID: eventID, EventID: eventID,
OddID: oddID, OddID: oddID,
@ -137,13 +181,14 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
} }
return newOutcome, nil return newOutcome, nil
} }
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) { func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) {
// You can move the loop over req.Outcomes and all the business logic here.
if len(req.Outcomes) > 30 { if len(req.Outcomes) > 30 {
s.mongoLogger.Error("too many outcomes",
zap.Int("count", len(req.Outcomes)),
zap.Int64("user_id", userID),
)
return domain.CreateBetRes{}, ErrOutcomeLimit return domain.CreateBetRes{}, ErrOutcomeLimit
} }
@ -153,17 +198,25 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
for _, outcomeReq := range req.Outcomes { for _, outcomeReq := range req.Outcomes {
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate outcome",
zap.Int64("event_id", outcomeReq.EventID),
zap.Int64("market_id", outcomeReq.MarketID),
zap.Int64("odd_id", outcomeReq.OddID),
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
totalOdds = totalOdds * float32(newOutcome.Odd) totalOdds *= float32(newOutcome.Odd)
outcomes = append(outcomes, newOutcome) outcomes = append(outcomes, newOutcome)
} }
// Handle role-specific logic and wallet deduction if needed.
var cashoutID string
cashoutID, err := s.GenerateCashoutID() cashoutID, err := s.GenerateCashoutID()
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate cashout ID",
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
@ -175,106 +228,117 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
CashoutID: cashoutID, CashoutID: cashoutID,
} }
switch role { switch role {
case domain.RoleCashier: case domain.RoleCashier:
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get branch by cashier",
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
// Deduct from wallet:
// TODO: Make this percentage come from the company deductedAmount := req.Amount / 10
var deductedAmount = req.Amount / 10
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
if err != nil { if err != nil {
s.mongoLogger.Error("failed to deduct from wallet",
zap.Int64("wallet_id", branch.WalletID),
zap.Float32("amount", deductedAmount),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
newBet.BranchID = domain.ValidInt64{
Value: branch.ID,
Valid: true,
}
newBet.CompanyID = domain.ValidInt64{ newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true}
Value: branch.CompanyID, newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true}
Valid: true, newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
}
newBet.UserID = domain.ValidInt64{
Value: userID,
Valid: true,
}
newBet.IsShopBet = true newBet.IsShopBet = true
// bet, err = s.betStore.CreateBet(ctx)
case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin: case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin:
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
// If a non cashier wants to create a bet, they will need to provide the Branch ID
if req.BranchID == nil { if req.BranchID == nil {
s.mongoLogger.Error("branch ID required for admin/manager",
zap.Int64("user_id", userID),
)
return domain.CreateBetRes{}, ErrBranchIDRequired return domain.CreateBetRes{}, ErrBranchIDRequired
} }
branch, err := s.branchSvc.GetBranchByID(ctx, *req.BranchID) branch, err := s.branchSvc.GetBranchByID(ctx, *req.BranchID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get branch by ID",
zap.Int64("branch_id", *req.BranchID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
// Deduct from wallet:
// TODO: Make this percentage come from the company deductedAmount := req.Amount / 10
var deductedAmount = req.Amount / 10
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
if err != nil { if err != nil {
s.mongoLogger.Error("wallet deduction failed",
zap.Int64("wallet_id", branch.WalletID),
zap.Float32("amount", deductedAmount),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
newBet.BranchID = domain.ValidInt64{ newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true}
Value: branch.ID, newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true}
Valid: true, newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
}
newBet.CompanyID = domain.ValidInt64{
Value: branch.CompanyID,
Valid: true,
}
newBet.UserID = domain.ValidInt64{
Value: userID,
Valid: true,
}
newBet.IsShopBet = true newBet.IsShopBet = true
case domain.RoleCustomer: case domain.RoleCustomer:
// Get User Wallet wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
wallet, err := s.walletSvc.GetWalletsByUser(ctx, userID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get customer wallets",
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
userWallet := wallet[0] userWallet := wallets[0]
err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount)) err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount))
if err != nil { if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer",
zap.Int64("wallet_id", userWallet.ID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
newBet.UserID = domain.ValidInt64{ newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
Value: userID,
Valid: true,
}
newBet.IsShopBet = false newBet.IsShopBet = false
default: default:
s.mongoLogger.Error("unknown role type",
zap.String("role", string(role)),
zap.Int64("user_id", userID),
)
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
} }
bet, err := s.CreateBet(ctx, newBet) bet, err := s.CreateBet(ctx, newBet)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to create bet",
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
// Associate outcomes with the bet.
for i := range outcomes { for i := range outcomes {
outcomes[i].BetID = bet.ID outcomes[i].BetID = bet.ID
} }
rows, err := s.betStore.CreateBetOutcome(ctx, outcomes) rows, err := s.betStore.CreateBetOutcome(ctx, outcomes)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to create bet outcomes",
zap.Int64("bet_id", bet.ID),
zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
@ -289,14 +353,24 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
var totalOdds float32 = 1 var totalOdds float32 = 1
markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID) markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID)
if err != nil { if err != nil {
s.logger.Error("failed to get odds for event", "event id", eventID, "error", err) s.logger.Error("failed to get odds for event", "event id", eventID, "error", err)
s.mongoLogger.Error("failed to get odds for event",
zap.String("eventID", eventID),
zap.Int32("sportID", sportID),
zap.String("homeTeam", HomeTeam),
zap.String("awayTeam", AwayTeam),
zap.Error(err))
return nil, 0, err return nil, 0, err
} }
if len(markets) == 0 { if len(markets) == 0 {
s.logger.Error("empty odds for event", "event id", eventID) s.logger.Error("empty odds for event", "event id", eventID)
s.mongoLogger.Warn("empty odds for event",
zap.String("eventID", eventID),
zap.Int32("sportID", sportID),
zap.String("homeTeam", HomeTeam),
zap.String("awayTeam", AwayTeam))
return nil, 0, fmt.Errorf("empty odds or event %v", eventID) return nil, 0, fmt.Errorf("empty odds or event %v", eventID)
} }
@ -325,35 +399,55 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
err = json.Unmarshal(rawBytes, &selectedOdd) err = json.Unmarshal(rawBytes, &selectedOdd)
if err != nil { if err != nil {
fmt.Printf("Failed to unmarshal raw odd %v", err) s.logger.Error("Failed to unmarshal raw odd", "error", err)
s.mongoLogger.Warn("Failed to unmarshal raw odd",
zap.String("eventID", eventID),
zap.Int32("sportID", sportID),
zap.Error(err))
continue continue
} }
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
if err != nil { if err != nil {
s.logger.Error("Failed to parse odd", "error", err) s.logger.Error("Failed to parse odd", "error", err)
s.mongoLogger.Warn("Failed to parse odd",
zap.String("eventID", eventID),
zap.String("oddValue", selectedOdd.Odds),
zap.Error(err))
continue continue
} }
eventID, err := strconv.ParseInt(eventID, 10, 64)
eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Failed to get event id", "error", err) s.logger.Error("Failed to parse eventID", "error", err)
s.mongoLogger.Warn("Failed to parse eventID",
zap.String("eventID", eventID),
zap.Error(err))
continue continue
} }
oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64) oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Failed to get odd id", "error", err) s.logger.Error("Failed to parse oddID", "error", err)
s.mongoLogger.Warn("Failed to parse oddID",
zap.String("oddID", selectedOdd.ID),
zap.Error(err))
continue continue
} }
marketID, err := strconv.ParseInt(market.MarketID, 10, 64) marketID, err := strconv.ParseInt(market.MarketID, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Failed to get odd id", "error", err) s.logger.Error("Failed to parse marketID", "error", err)
s.mongoLogger.Warn("Failed to parse marketID",
zap.String("marketID", market.MarketID),
zap.Error(err))
continue continue
} }
marketName := market.MarketName marketName := market.MarketName
newOdds = append(newOdds, domain.CreateBetOutcome{ newOdds = append(newOdds, domain.CreateBetOutcome{
EventID: eventID, EventID: eventIDInt,
OddID: oddID, OddID: oddID,
MarketID: marketID, MarketID: marketID,
SportID: int64(sportID), SportID: int64(sportID),
@ -367,15 +461,27 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
Expires: StartTime, Expires: StartTime,
}) })
totalOdds = totalOdds * float32(parsedOdd) totalOdds *= float32(parsedOdd)
} }
if len(newOdds) == 0 { if len(newOdds) == 0 {
s.logger.Error("Bet Outcomes is empty for market", "selectedMarket", selectedMarkets[0].MarketName) s.logger.Error("Bet Outcomes is empty for market", "selectedMarkets", len(selectedMarkets))
s.mongoLogger.Error("Bet Outcomes is empty for market",
zap.String("eventID", eventID),
zap.Int32("sportID", sportID),
zap.String("homeTeam", HomeTeam),
zap.String("awayTeam", AwayTeam),
zap.Int("selectedMarkets", len(selectedMarkets)))
return nil, 0, ErrGenerateRandomOutcome return nil, 0, ErrGenerateRandomOutcome
} }
// ✅ Final success log (optional)
s.mongoLogger.Info("Random bet outcomes generated successfully",
zap.String("eventID", eventID),
zap.Int32("sportID", sportID),
zap.Int("numOutcomes", len(newOdds)),
zap.Float32("totalOdds", totalOdds))
return newOdds, totalOdds, nil return newOdds, totalOdds, nil
} }
@ -387,10 +493,17 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
domain.ValidInt64{}, domain.ValidInt64{}, leagueID, sportID, firstStartTime, lastStartTime) domain.ValidInt64{}, domain.ValidInt64{}, leagueID, sportID, firstStartTime, lastStartTime)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get paginated upcoming events",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID),
zap.Error(err))
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
if len(events) == 0 { if len(events) == 0 {
s.mongoLogger.Warn("no events available for random bet",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID))
return domain.CreateBetRes{}, ErrNoEventsAvailable return domain.CreateBetRes{}, ErrNoEventsAvailable
} }
@ -417,6 +530,11 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
if err != nil { if err != nil {
s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err) s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err)
s.mongoLogger.Error("failed to generate random bet outcome",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID),
zap.String("eventID", event.ID),
zap.String("error", fmt.Sprintf("%v", err)))
continue continue
} }
@ -426,6 +544,9 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
} }
if len(randomOdds) == 0 { if len(randomOdds) == 0 {
s.logger.Error("Failed to generate random any outcomes for all events") s.logger.Error("Failed to generate random any outcomes for all events")
s.mongoLogger.Error("Failed to generate random any outcomes for all events",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID))
return domain.CreateBetRes{}, ErrGenerateRandomOutcome return domain.CreateBetRes{}, ErrGenerateRandomOutcome
} }
@ -435,6 +556,9 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
cashoutID, err = s.GenerateCashoutID() cashoutID, err = s.GenerateCashoutID()
if err != nil { if err != nil {
s.mongoLogger.Error("Failed to generate cash out ID",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID))
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
@ -452,6 +576,10 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
bet, err := s.CreateBet(ctx, newBet) bet, err := s.CreateBet(ctx, newBet)
if err != nil { if err != nil {
s.mongoLogger.Error("Failed to create a new random bet",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID),
zap.String("bet", fmt.Sprintf("%+v", newBet)))
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
@ -461,11 +589,19 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds) rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds)
if err != nil { if err != nil {
s.mongoLogger.Error("Failed to create a new random bet outcome",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID),
zap.String("randomOdds", fmt.Sprintf("%+v", randomOdds)))
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
res := domain.ConvertCreateBet(bet, rows) res := domain.ConvertCreateBet(bet, rows)
s.mongoLogger.Info("Random bets placed successfully",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID),
zap.String("response", fmt.Sprintf("%+v", res)))
return res, nil return res, nil
} }
@ -500,10 +636,12 @@ func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) e
} }
func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
bet, err := s.GetBetByID(ctx, id) bet, err := s.GetBetByID(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to update bet status. Invalid bet id") s.mongoLogger.Error("failed to update bet status: invalid bet ID",
zap.Int64("bet_id", id),
zap.Error(err),
)
return err return err
} }
@ -516,22 +654,30 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id) customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to update bet status. Invalid customer wallet id") s.mongoLogger.Error("failed to get customer wallet",
zap.Int64("bet_id", id),
zap.Error(err),
)
return err return err
} }
var amount domain.Currency var amount domain.Currency
if status == domain.OUTCOME_STATUS_WIN { switch status {
case domain.OUTCOME_STATUS_WIN:
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds)
} else if status == domain.OUTCOME_STATUS_HALF { case domain.OUTCOME_STATUS_HALF:
amount = (domain.CalculateWinnings(bet.Amount, bet.TotalOdds)) / 2 amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) / 2
} else { default:
amount = bet.Amount amount = bet.Amount
} }
err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount)
err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount)
if err != nil { if err != nil {
s.logger.Error("Failed to update bet status. Failed to update user wallet") s.mongoLogger.Error("failed to add winnings to wallet",
zap.Int64("wallet_id", customerWallet.RegularID),
zap.Float32("amount", float32(amount)),
zap.Error(err),
)
return err return err
} }
@ -541,92 +687,89 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) { func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) {
betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID) betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get bet outcomes",
zap.Int64("bet_id", betID),
zap.Error(err),
)
return domain.OUTCOME_STATUS_PENDING, err return domain.OUTCOME_STATUS_PENDING, err
} }
status := domain.OUTCOME_STATUS_PENDING status := domain.OUTCOME_STATUS_PENDING
for _, betOutcome := range betOutcomes { for _, betOutcome := range betOutcomes {
// If any of the bet outcomes are pending return
if betOutcome.Status == domain.OUTCOME_STATUS_PENDING { if betOutcome.Status == domain.OUTCOME_STATUS_PENDING {
s.mongoLogger.Info("outcome still pending",
zap.Int64("bet_id", betID),
)
return domain.OUTCOME_STATUS_PENDING, ErrOutcomesNotCompleted return domain.OUTCOME_STATUS_PENDING, ErrOutcomesNotCompleted
} }
if betOutcome.Status == domain.OUTCOME_STATUS_ERROR { if betOutcome.Status == domain.OUTCOME_STATUS_ERROR {
s.mongoLogger.Info("outcome contains error",
zap.Int64("bet_id", betID),
)
return domain.OUTCOME_STATUS_ERROR, nil return domain.OUTCOME_STATUS_ERROR, nil
} }
// The bet status can only be updated if its not lost or error
// If all the bet outcomes are a win, then set the bet status to win
// If even one of the bet outcomes is a loss then set the bet status to loss
// If even one of the bet outcomes is an error, then set the bet status to error
switch status { switch status {
case domain.OUTCOME_STATUS_PENDING: case domain.OUTCOME_STATUS_PENDING:
status = betOutcome.Status status = betOutcome.Status
case domain.OUTCOME_STATUS_WIN: case domain.OUTCOME_STATUS_WIN:
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS { switch betOutcome.Status {
case domain.OUTCOME_STATUS_LOSS:
status = domain.OUTCOME_STATUS_LOSS status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF { case domain.OUTCOME_STATUS_HALF:
status = domain.OUTCOME_STATUS_HALF status = domain.OUTCOME_STATUS_HALF
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN { case domain.OUTCOME_STATUS_VOID:
status = domain.OUTCOME_STATUS_WIN
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
status = domain.OUTCOME_STATUS_VOID status = domain.OUTCOME_STATUS_VOID
} else { case domain.OUTCOME_STATUS_WIN:
// remain win
default:
status = domain.OUTCOME_STATUS_ERROR status = domain.OUTCOME_STATUS_ERROR
} }
case domain.OUTCOME_STATUS_LOSS: case domain.OUTCOME_STATUS_LOSS:
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS { // stay as LOSS regardless of others
status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN {
status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
status = domain.OUTCOME_STATUS_LOSS
} else {
status = domain.OUTCOME_STATUS_ERROR
}
case domain.OUTCOME_STATUS_VOID: case domain.OUTCOME_STATUS_VOID:
if betOutcome.Status == domain.OUTCOME_STATUS_VOID || switch betOutcome.Status {
betOutcome.Status == domain.OUTCOME_STATUS_WIN || case domain.OUTCOME_STATUS_LOSS:
betOutcome.Status == domain.OUTCOME_STATUS_HALF {
status = domain.OUTCOME_STATUS_VOID
} else if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
status = domain.OUTCOME_STATUS_LOSS status = domain.OUTCOME_STATUS_LOSS
case domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_HALF, domain.OUTCOME_STATUS_VOID:
} else { // remain VOID
default:
status = domain.OUTCOME_STATUS_ERROR status = domain.OUTCOME_STATUS_ERROR
} }
case domain.OUTCOME_STATUS_HALF: case domain.OUTCOME_STATUS_HALF:
if betOutcome.Status == domain.OUTCOME_STATUS_HALF || switch betOutcome.Status {
betOutcome.Status == domain.OUTCOME_STATUS_WIN { case domain.OUTCOME_STATUS_LOSS:
status = domain.OUTCOME_STATUS_HALF
} else if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
status = domain.OUTCOME_STATUS_LOSS status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID { case domain.OUTCOME_STATUS_VOID:
status = domain.OUTCOME_STATUS_VOID status = domain.OUTCOME_STATUS_VOID
} else { case domain.OUTCOME_STATUS_HALF, domain.OUTCOME_STATUS_WIN:
// remain HALF
default:
status = domain.OUTCOME_STATUS_ERROR status = domain.OUTCOME_STATUS_ERROR
} }
default: default:
// If the status is not pending, win, loss or error, then set the status to error
status = domain.OUTCOME_STATUS_ERROR status = domain.OUTCOME_STATUS_ERROR
} }
} }
if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR { if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR {
// If the status is pending or error, then we don't need to update the bet s.mongoLogger.Info("bet status not updated due to status",
s.logger.Info("bet not updated", "bet id", betID, "status", status) zap.Int64("bet_id", betID),
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("Error when processing bet outcomes") zap.String("final_status", string(status)),
)
} }
return status, nil return status, nil
} }
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) { func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status) betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to update bet outcome status",
zap.Int64("betID", id),
zap.Error(err),
)
return domain.BetOutcome{}, err return domain.BetOutcome{}, err
} }

View File

@ -1,4 +1,4 @@
package mongoLogger package handlers
import ( import (
"context" "context"

View File

@ -52,7 +52,14 @@ func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
}) })
} }
return c.Status(fiber.StatusOK).JSON(summary) return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Dashboard reports generated successfully",
Success: true,
StatusCode: 200,
Data: summary,
})
// return c.Status(fiber.StatusOK).JSON(summary)
} }
// parseReportFilter parses query parameters into ReportFilter // parseReportFilter parses query parameters into ReportFilter

View File

@ -7,7 +7,7 @@ import (
_ "github.com/SamuelTariku/FortuneBet-Backend/docs" _ "github.com/SamuelTariku/FortuneBet-Backend/docs"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger" // "github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor" // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor"
@ -230,7 +230,7 @@ func (a *App) initAppRoutes() {
//mongoDB logs //mongoDB logs
ctx := context.Background() ctx := context.Background()
group.Get("/logs", mongoLogger.GetLogsHandler(ctx)) group.Get("/logs", handlers.GetLogsHandler(ctx))
// Recommendation Routes // Recommendation Routes
group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations)