1385 lines
39 KiB
Go
1385 lines
39 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/ports"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Interface for creating new bet store
|
|
func NewBetStore(s *Store) ports.BetStore { return s }
|
|
|
|
func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
|
|
newBet, err := s.queries.CreateBet(ctx, domain.ConvertCreateBet(bet))
|
|
if err != nil {
|
|
return domain.Bet{}, err
|
|
}
|
|
return domain.ConvertDBBet(newBet), err
|
|
|
|
}
|
|
|
|
func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) {
|
|
var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes))
|
|
|
|
for _, outcome := range outcomes {
|
|
dbParams = append(dbParams, domain.ConvertDBCreateBetOutcome(outcome))
|
|
}
|
|
|
|
rows, err := s.queries.CreateBetOutcome(ctx, dbParams)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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, nil
|
|
}
|
|
|
|
func (s *Store) CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error) {
|
|
createFlag := dbgen.CreateFlagParams{
|
|
BetID: pgtype.Int8{
|
|
Int64: flag.BetID,
|
|
Valid: flag.BetID != 0,
|
|
},
|
|
OddsMarketID: pgtype.Int8{
|
|
Int64: flag.OddID,
|
|
Valid: flag.OddID != 0,
|
|
},
|
|
Reason: pgtype.Text{
|
|
String: flag.Reason,
|
|
Valid: true,
|
|
},
|
|
}
|
|
|
|
f, err := s.queries.CreateFlag(ctx, createFlag)
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to create flag",
|
|
zap.String("flag", f.Reason.String),
|
|
zap.Any("flag_id", f.ID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.Flag{}, err
|
|
}
|
|
|
|
return domain.ConvertDBFlag(f), nil
|
|
}
|
|
|
|
func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
|
|
bet, err := s.queries.GetBetByID(ctx, id)
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to get bet by ID",
|
|
zap.Int64("bet_id", id),
|
|
zap.Error(err),
|
|
)
|
|
return domain.GetBet{}, err
|
|
}
|
|
|
|
return domain.ConvertDBBetWithOutcomes(bet), nil
|
|
}
|
|
|
|
func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, int64, error) {
|
|
bets, err := s.queries.GetAllBets(ctx, dbgen.GetAllBetsParams{
|
|
UserID: filter.UserID.ToPG(),
|
|
CompanyID: filter.CompanyID.ToPG(),
|
|
Status: filter.Status.ToPG(),
|
|
CashedOut: filter.CashedOut.ToPG(),
|
|
IsShopBet: filter.IsShopBet.ToPG(),
|
|
Query: filter.Query.ToPG(),
|
|
CreatedBefore: filter.CreatedBefore.ToPG(),
|
|
CreatedAfter: filter.CreatedAfter.ToPG(),
|
|
Offset: pgtype.Int4{
|
|
Int32: int32(filter.Offset.Value * filter.Limit.Value),
|
|
Valid: filter.Offset.Valid,
|
|
},
|
|
Limit: filter.Limit.ToPG(),
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to get all bets",
|
|
zap.Any("filter", filter),
|
|
zap.Error(err),
|
|
)
|
|
return nil, 0, err
|
|
}
|
|
|
|
total, err := s.queries.GetTotalBets(ctx, dbgen.GetTotalBetsParams{
|
|
UserID: filter.UserID.ToPG(),
|
|
CompanyID: filter.CompanyID.ToPG(),
|
|
Status: filter.Status.ToPG(),
|
|
CashedOut: filter.CashedOut.ToPG(),
|
|
IsShopBet: filter.IsShopBet.ToPG(),
|
|
Query: filter.Query.ToPG(),
|
|
CreatedBefore: filter.CreatedBefore.ToPG(),
|
|
CreatedAfter: filter.CreatedAfter.ToPG(),
|
|
})
|
|
|
|
if err != nil {
|
|
// domain.MongoDBLogger.Error("failed to get all bets",
|
|
// zap.Any("filter", filter),
|
|
// zap.Error(err),
|
|
// )
|
|
return nil, 0, err
|
|
}
|
|
|
|
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
|
for _, bet := range bets {
|
|
result = append(result, domain.ConvertDBBetWithOutcomes(bet))
|
|
}
|
|
|
|
return result, total, nil
|
|
}
|
|
|
|
func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) {
|
|
bets, err := s.queries.GetBetByUserID(ctx, UserID)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
|
for _, bet := range bets {
|
|
result = append(result, domain.ConvertDBBetWithOutcomes(bet))
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) {
|
|
bet, err := s.queries.GetBetByFastCode(ctx, fastcode)
|
|
|
|
if err != nil {
|
|
return domain.GetBet{}, err
|
|
}
|
|
|
|
return domain.ConvertDBBetWithOutcomes(bet), nil
|
|
}
|
|
|
|
func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error) {
|
|
bets, err := s.queries.GetBetsForCashback(ctx)
|
|
var res []domain.GetBet
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, bet := range bets {
|
|
cashbackBet := domain.ConvertDBBetWithOutcomes(bet)
|
|
res = append(res, cashbackBet)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Store) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
|
count, err := s.queries.GetBetCountByUserID(ctx, dbgen.GetBetCountByUserIDParams{
|
|
UserID: UserID,
|
|
OutcomesHash: outcomesHash,
|
|
})
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Store) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
|
|
count, err := s.queries.GetBetCountByOutcomesHash(ctx, outcomesHash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Store) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) {
|
|
count, err := s.queries.GetBetOutcomeCountByOddID(ctx, oddID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
|
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
|
|
ID: id,
|
|
CashedOut: cashedOut,
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update cashout",
|
|
zap.Int64("id", id),
|
|
zap.Bool("cashed_out", cashedOut),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
|
err := s.queries.UpdateStatus(ctx, dbgen.UpdateStatusParams{
|
|
ID: id,
|
|
Status: int32(status),
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update status",
|
|
zap.Int64("id", id),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *Store) SettleWinningBet(ctx context.Context, betID int64, userID int64, amount domain.Currency, status domain.OutcomeStatus) error {
|
|
tx, err := s.conn.BeginTx(ctx, pgx.TxOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
qtx := s.queries.WithTx(tx)
|
|
|
|
wallet, err := qtx.GetCustomerWallet(ctx, userID)
|
|
if err != nil {
|
|
tx.Rollback(ctx)
|
|
return err
|
|
}
|
|
|
|
// 1. Update wallet
|
|
newAmount := wallet.RegularBalance + int64(amount)
|
|
if err := qtx.UpdateBalance(ctx, dbgen.UpdateBalanceParams{
|
|
Balance: newAmount,
|
|
ID: wallet.RegularID,
|
|
}); err != nil {
|
|
tx.Rollback(ctx)
|
|
return err
|
|
}
|
|
|
|
// 2. Update bet
|
|
if err := qtx.UpdateStatus(ctx, dbgen.UpdateStatusParams{
|
|
Status: int32(status),
|
|
ID: betID,
|
|
}); err != nil {
|
|
tx.Rollback(ctx)
|
|
return err
|
|
}
|
|
|
|
// 3. Commit both together
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) {
|
|
|
|
outcomes, err := s.queries.GetBetOutcomeViewByEventID(ctx, dbgen.GetBetOutcomeViewByEventIDParams{
|
|
EventID: eventID,
|
|
FilterStatus: filter.OutcomeStatus.ToPG(),
|
|
CompanyID: filter.CompanyID.ToPG(),
|
|
Offset: filter.Offset.ToPG(),
|
|
Limit: filter.Limit.ToPG(),
|
|
})
|
|
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
|
|
zap.Int64("event_id", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return nil, 0, err
|
|
}
|
|
|
|
total, err := s.queries.TotalBetOutcomeViewByEventID(ctx, dbgen.TotalBetOutcomeViewByEventIDParams{
|
|
EventID: eventID,
|
|
FilterStatus: filter.OutcomeStatus.ToPG(),
|
|
CompanyID: filter.CompanyID.ToPG(),
|
|
})
|
|
|
|
var result []domain.BetOutcomeViewRes = make([]domain.BetOutcomeViewRes, 0, len(outcomes))
|
|
for _, outcome := range outcomes {
|
|
result = append(result, domain.ConvertDBBetOutcomesView(outcome))
|
|
}
|
|
return result, total, nil
|
|
}
|
|
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
|
|
|
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{
|
|
EventID: eventID,
|
|
FilterStatus: pgtype.Int4{
|
|
Int32: int32(domain.OUTCOME_STATUS_PENDING),
|
|
Valid: is_filtered,
|
|
},
|
|
FilterStatus2: pgtype.Int4{
|
|
Int32: int32(domain.OUTCOME_STATUS_ERROR),
|
|
Valid: is_filtered,
|
|
},
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.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))
|
|
for _, outcome := range outcomes {
|
|
result = append(result, domain.ConvertDBBetOutcomes(outcome))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) {
|
|
outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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))
|
|
for _, outcome := range outcomes {
|
|
result = append(result, domain.ConvertDBBetOutcomes(outcome))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
|
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
|
Status: int32(status),
|
|
ID: id,
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status",
|
|
zap.Int64("id", id),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BetOutcome{}, err
|
|
}
|
|
|
|
res := domain.ConvertDBBetOutcomes(update)
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
|
update, err := s.queries.UpdateBetOutcomeStatusByBetID(ctx, dbgen.UpdateBetOutcomeStatusByBetIDParams{
|
|
Status: int32(status),
|
|
BetID: id,
|
|
})
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status",
|
|
zap.Int64("id", id),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BetOutcome{}, err
|
|
}
|
|
|
|
res := domain.ConvertDBBetOutcomes(update)
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
|
|
outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{
|
|
EventID: eventID,
|
|
Status: int32(status),
|
|
})
|
|
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status for event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
return nil, err
|
|
}
|
|
|
|
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
|
|
for _, outcome := range outcomes {
|
|
result = append(result, domain.ConvertDBBetOutcomes(outcome))
|
|
}
|
|
return result, nil
|
|
}
|
|
func (s *Store) UpdateBetOutcomeStatusForOddId(ctx context.Context, oddID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
|
|
outcomes, err := s.queries.UpdateBetOutcomeStatusForOddID(ctx, dbgen.UpdateBetOutcomeStatusForOddIDParams{
|
|
OddID: oddID,
|
|
Status: int32(status),
|
|
})
|
|
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status for oddID",
|
|
zap.Int64("oddId", oddID),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
return nil, err
|
|
}
|
|
|
|
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
|
|
for _, outcome := range outcomes {
|
|
result = append(result, domain.ConvertDBBetOutcomes(outcome))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Store) BulkUpdateBetOutcomeStatusForOddIds(ctx context.Context, oddID []int64, status domain.OutcomeStatus) error {
|
|
err := s.queries.BulkUpdateBetOutcomeStatusByOddIDs(ctx, dbgen.BulkUpdateBetOutcomeStatusByOddIDsParams{
|
|
Status: int32(status),
|
|
OddIds: oddID,
|
|
})
|
|
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status for oddIDs",
|
|
zap.Int64s("oddIds", oddID),
|
|
zap.Int32("status", int32(status)),
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) UpdateBetWithCashback(ctx context.Context, betID int64, cashbackStatus bool) error {
|
|
err := s.queries.UpdateBetWithCashback(ctx, dbgen.UpdateBetWithCashbackParams{
|
|
ID: betID,
|
|
Processed: cashbackStatus,
|
|
})
|
|
|
|
if err != nil {
|
|
domain.MongoDBLogger.Error("failed to update bet outcome status for event",
|
|
zap.Int64("betID", betID),
|
|
zap.Bool("status", cashbackStatus),
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetBetSummary returns aggregated bet statistics
|
|
func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
|
|
totalStakes domain.Currency,
|
|
totalBets int64,
|
|
activeBets int64,
|
|
totalWins int64,
|
|
totalLosses int64,
|
|
winBalance domain.Currency,
|
|
err error,
|
|
) {
|
|
query := `SELECT
|
|
COALESCE(SUM(amount), 0) as total_stakes,
|
|
COALESCE(COUNT(*), 0) as total_bets,
|
|
COALESCE(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END), 0) as active_bets,
|
|
COALESCE(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END), 0) as total_wins,
|
|
COALESCE(SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END), 0) as total_losses,
|
|
COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as win_balance
|
|
FROM bets`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
// if filter.CompanyID.Valid {
|
|
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
|
|
// args = append(args, filter.CompanyID.Value)
|
|
// argPos++
|
|
// }
|
|
// if filter.BranchID.Valid {
|
|
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
|
|
// if len(args) == 0 {
|
|
// return " WHERE "
|
|
// }
|
|
// return " AND "
|
|
// }(), argPos)
|
|
// args = append(args, filter.BranchID.Value)
|
|
// argPos++
|
|
// }
|
|
if filter.UserID.Valid {
|
|
query += fmt.Sprintf(" %suser_id = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.UserID.Value)
|
|
argPos++
|
|
}
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
row := s.conn.QueryRow(ctx, query, args...)
|
|
err = row.Scan(&totalStakes, &totalBets, &activeBets, &totalWins, &totalLosses, &winBalance)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
|
|
// domain.MongoDBLogger.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
|
|
}
|
|
|
|
// GetBetStats returns bet statistics grouped by date
|
|
func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]domain.BetStat, error) {
|
|
query := `SELECT
|
|
DATE(created_at) as date,
|
|
COUNT(*) as total_bets,
|
|
COALESCE(SUM(amount), 0) as total_stakes,
|
|
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
|
|
COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts,
|
|
AVG(total_odds) as average_odds
|
|
FROM bets`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
// if filter.CompanyID.Valid {
|
|
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
|
|
// args = append(args, filter.CompanyID.Value)
|
|
// argPos++
|
|
// }
|
|
// if filter.BranchID.Valid {
|
|
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
|
|
// if len(args) == 0 {
|
|
// return " WHERE "
|
|
// }
|
|
// return " AND "
|
|
// }(), argPos)
|
|
// args = append(args, filter.BranchID.Value)
|
|
// argPos++
|
|
// }
|
|
if filter.UserID.Valid {
|
|
query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.UserID.Value)
|
|
argPos++
|
|
}
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += " GROUP BY DATE(created_at) ORDER BY DATE(created_at)"
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var stats []domain.BetStat
|
|
for rows.Next() {
|
|
var stat domain.BetStat
|
|
if err := rows.Scan(
|
|
&stat.Date,
|
|
&stat.TotalBets,
|
|
&stat.TotalStakes,
|
|
&stat.TotalWins,
|
|
&stat.TotalPayouts,
|
|
&stat.AverageOdds,
|
|
); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan bet stat",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to scan bet stat: %w", err)
|
|
}
|
|
stats = append(stats, stat)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetBetStats executed successfully",
|
|
zap.Int("result_count", len(stats)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return stats, nil
|
|
}
|
|
|
|
// GetSportPopularity returns the most popular sport by date
|
|
func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilter) (map[time.Time]string, error) {
|
|
query := `WITH sport_counts AS (
|
|
SELECT
|
|
DATE(b.created_at) as date,
|
|
bo.sport_id,
|
|
COUNT(*) as bet_count,
|
|
ROW_NUMBER() OVER (PARTITION BY DATE(b.created_at) ORDER BY COUNT(*) DESC) as rank
|
|
FROM bets b
|
|
JOIN bet_outcomes bo ON b.id = bo.bet_id
|
|
WHERE bo.sport_id IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
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 {
|
|
query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND b.status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += ` GROUP BY DATE(b.created_at), bo.sport_id
|
|
)
|
|
SELECT date, sport_id FROM sport_counts WHERE rank = 1`
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
popularity := make(map[time.Time]string)
|
|
for rows.Next() {
|
|
var date time.Time
|
|
var sportID string
|
|
if err := rows.Scan(&date, &sportID); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan sport popularity",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to scan sport popularity: %w", err)
|
|
}
|
|
popularity[date] = sportID
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetSportPopularity executed successfully",
|
|
zap.Int("result_count", len(popularity)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return popularity, nil
|
|
}
|
|
|
|
// GetMarketPopularity returns the most popular market by date
|
|
func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[time.Time]string, error) {
|
|
query := `WITH market_counts AS (
|
|
SELECT
|
|
DATE(b.created_at) as date,
|
|
bo.market_name,
|
|
COUNT(*) as bet_count,
|
|
ROW_NUMBER() OVER (PARTITION BY DATE(b.created_at) ORDER BY COUNT(*) DESC) as rank
|
|
FROM bets b
|
|
JOIN bet_outcomes bo ON b.id = bo.bet_id
|
|
WHERE bo.market_name IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
if filter.UserID.Valid {
|
|
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
|
|
args = append(args, filter.UserID.Value)
|
|
argPos++
|
|
}
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND b.status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += ` GROUP BY DATE(b.created_at), bo.market_name
|
|
)
|
|
SELECT date, market_name FROM market_counts WHERE rank = 1`
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
popularity := make(map[time.Time]string)
|
|
for rows.Next() {
|
|
var date time.Time
|
|
var marketName string
|
|
if err := rows.Scan(&date, &marketName); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan market popularity",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to scan market popularity: %w", err)
|
|
}
|
|
popularity[date] = marketName
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetMarketPopularity executed successfully",
|
|
zap.Int("result_count", len(popularity)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return popularity, nil
|
|
}
|
|
|
|
// GetExtremeValues returns the highest stake and payout by date
|
|
func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter) (map[time.Time]domain.ExtremeValues, error) {
|
|
query := `SELECT
|
|
DATE(created_at) as date,
|
|
MAX(amount) as highest_stake,
|
|
MAX(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END) as highest_payout
|
|
FROM bets`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
// if filter.CompanyID.Valid {
|
|
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
|
|
// args = append(args, filter.CompanyID.Value)
|
|
// argPos++
|
|
// }
|
|
// if filter.BranchID.Valid {
|
|
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
|
|
// if len(args) == 0 {
|
|
// return " WHERE "
|
|
// }
|
|
// return " AND "
|
|
// }(), argPos)
|
|
// args = append(args, filter.BranchID.Value)
|
|
// argPos++
|
|
// }
|
|
if filter.UserID.Valid {
|
|
query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.UserID.Value)
|
|
argPos++
|
|
}
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
|
|
if len(args) == 0 {
|
|
return " WHERE "
|
|
}
|
|
return " AND "
|
|
}(), argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += " GROUP BY DATE(created_at)"
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
extremes := make(map[time.Time]domain.ExtremeValues)
|
|
for rows.Next() {
|
|
var date time.Time
|
|
var extreme domain.ExtremeValues
|
|
if err := rows.Scan(&date, &extreme.HighestStake, &extreme.HighestPayout); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan extreme values",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to scan extreme values: %w", err)
|
|
}
|
|
extremes[date] = extreme
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetExtremeValues executed successfully",
|
|
zap.Int("result_count", len(extremes)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return extremes, nil
|
|
}
|
|
|
|
// GetCustomerBetActivity returns bet activity by customer
|
|
func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.CustomerBetActivity, error) {
|
|
query := `SELECT
|
|
user_id as customer_id,
|
|
COUNT(*) as total_bets,
|
|
COALESCE(SUM(amount), 0) as total_stakes,
|
|
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
|
|
COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts,
|
|
MIN(created_at) as first_bet_date,
|
|
MAX(created_at) as last_bet_date,
|
|
AVG(total_odds) as average_odds
|
|
FROM bets
|
|
WHERE user_id IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
// if filter.CompanyID.Valid {
|
|
// query += fmt.Sprintf(" AND company_id = $%d", argPos)
|
|
// args = append(args, filter.CompanyID.Value)
|
|
// argPos++
|
|
// }
|
|
// if filter.BranchID.Valid {
|
|
// query += fmt.Sprintf(" AND branch_id = $%d", argPos)
|
|
// args = append(args, filter.BranchID.Value)
|
|
// argPos++
|
|
// }
|
|
if filter.UserID.Valid {
|
|
query += fmt.Sprintf(" AND user_id = $%d", argPos)
|
|
args = append(args, filter.UserID.Value)
|
|
argPos++
|
|
}
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += " GROUP BY user_id"
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var activities []domain.CustomerBetActivity
|
|
for rows.Next() {
|
|
var activity domain.CustomerBetActivity
|
|
if err := rows.Scan(
|
|
&activity.CustomerID,
|
|
&activity.TotalBets,
|
|
&activity.TotalStakes,
|
|
&activity.TotalWins,
|
|
&activity.TotalPayouts,
|
|
&activity.FirstBetDate,
|
|
&activity.LastBetDate,
|
|
&activity.AverageOdds,
|
|
); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan customer bet activity",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to scan customer bet activity: %w", err)
|
|
}
|
|
activities = append(activities, activity)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetCustomerBetActivity executed successfully",
|
|
zap.Int("result_count", len(activities)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return activities, nil
|
|
}
|
|
|
|
// GetBranchBetActivity returns bet activity by branch
|
|
func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.BranchBetActivity, error) {
|
|
query := `SELECT
|
|
branch_id,
|
|
COUNT(*) as total_bets,
|
|
COALESCE(SUM(amount), 0) as total_stakes,
|
|
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
|
|
COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts
|
|
FROM bets
|
|
WHERE branch_id IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// Add filters if provided
|
|
// if filter.CompanyID.Valid {
|
|
// query += fmt.Sprintf(" AND company_id = $%d", argPos)
|
|
// args = append(args, filter.CompanyID.Value)
|
|
// argPos++
|
|
// }
|
|
// if filter.BranchID.Valid {
|
|
// query += fmt.Sprintf(" AND branch_id = $%d", argPos)
|
|
// args = append(args, filter.BranchID.Value)
|
|
// argPos++
|
|
// }
|
|
if filter.StartTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += " GROUP BY branch_id"
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var activities []domain.BranchBetActivity
|
|
for rows.Next() {
|
|
var activity domain.BranchBetActivity
|
|
if err := rows.Scan(
|
|
&activity.BranchID,
|
|
&activity.TotalBets,
|
|
&activity.TotalStakes,
|
|
&activity.TotalWins,
|
|
&activity.TotalPayouts,
|
|
); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan branch bet activity", zap.Error(err))
|
|
return nil, fmt.Errorf("failed to scan branch bet activity: %w", err)
|
|
}
|
|
activities = append(activities, activity)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetBranchBetActivity executed successfully",
|
|
zap.Int("result_count", len(activities)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return activities, nil
|
|
}
|
|
|
|
// GetSportBetActivity returns bet activity by sport
|
|
func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.SportBetActivity, error) {
|
|
query := `SELECT
|
|
bo.sport_id,
|
|
COUNT(*) as total_bets,
|
|
COALESCE(SUM(b.amount), 0) as total_stakes,
|
|
SUM(CASE WHEN b.status = 1 THEN 1 ELSE 0 END) as total_wins,
|
|
COALESCE(SUM(CASE WHEN b.status = 1 THEN b.amount * b.total_odds ELSE 0 END), 0) as total_payouts,
|
|
AVG(b.total_odds) as average_odds
|
|
FROM bets b
|
|
JOIN bet_outcomes bo ON b.id = bo.bet_id
|
|
WHERE bo.sport_id IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// 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 {
|
|
query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND b.status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += " GROUP BY bo.sport_id"
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var activities []domain.SportBetActivity
|
|
for rows.Next() {
|
|
var activity domain.SportBetActivity
|
|
if err := rows.Scan(
|
|
&activity.SportID,
|
|
&activity.TotalBets,
|
|
&activity.TotalStakes,
|
|
&activity.TotalWins,
|
|
&activity.TotalPayouts,
|
|
&activity.AverageOdds,
|
|
); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan sport bet activity", zap.Error(err))
|
|
return nil, fmt.Errorf("failed to scan sport bet activity: %w", err)
|
|
}
|
|
activities = append(activities, activity)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetSportBetActivity executed successfully",
|
|
zap.Int("result_count", len(activities)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
return activities, nil
|
|
}
|
|
|
|
// GetSportDetails returns sport names by ID
|
|
func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter) (map[string]string, error) {
|
|
query := `SELECT DISTINCT bo.sport_id, e.match_name
|
|
FROM bet_outcomes bo
|
|
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`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// 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 {
|
|
query += fmt.Sprintf(" AND bo.created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND bo.created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
details := make(map[string]string)
|
|
for rows.Next() {
|
|
var sportID, matchName string
|
|
if err := rows.Scan(&sportID, &matchName); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan sport detail", zap.Error(err))
|
|
return nil, fmt.Errorf("failed to scan sport detail: %w", err)
|
|
}
|
|
details[sportID] = matchName
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetSportDetails executed successfully",
|
|
zap.Int("result_count", len(details)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
|
|
return details, nil
|
|
}
|
|
|
|
// GetSportMarketPopularity returns most popular market by sport
|
|
func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[string]string, error) {
|
|
query := `WITH market_counts AS (
|
|
SELECT
|
|
bo.sport_id,
|
|
bo.market_name,
|
|
COUNT(*) AS bet_count,
|
|
ROW_NUMBER() OVER (PARTITION BY bo.sport_id ORDER BY COUNT(*) DESC) as rank
|
|
FROM bets b
|
|
JOIN bet_outcomes bo ON b.id = bo.bet_id
|
|
WHERE bo.sport_id IS NOT NULL AND bo.market_name IS NOT NULL`
|
|
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
// 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 {
|
|
query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
|
|
args = append(args, filter.StartTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.EndTime.Valid {
|
|
query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
|
|
args = append(args, filter.EndTime.Value)
|
|
argPos++
|
|
}
|
|
if filter.Status.Valid {
|
|
query += fmt.Sprintf(" AND b.status = $%d", argPos)
|
|
args = append(args, filter.Status.Value)
|
|
argPos++
|
|
}
|
|
|
|
query += ` GROUP BY bo.sport_id, bo.market_name
|
|
)
|
|
SELECT sport_id, market_name FROM market_counts WHERE rank = 1`
|
|
|
|
rows, err := s.conn.Query(ctx, query, args...)
|
|
if err != nil {
|
|
domain.MongoDBLogger.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)
|
|
}
|
|
defer rows.Close()
|
|
|
|
popularity := make(map[string]string)
|
|
for rows.Next() {
|
|
var sportID, marketName string
|
|
if err := rows.Scan(&sportID, &marketName); err != nil {
|
|
domain.MongoDBLogger.Error("failed to scan sport market popularity", zap.Error(err))
|
|
return nil, fmt.Errorf("failed to scan sport market popularity: %w", err)
|
|
}
|
|
popularity[sportID] = marketName
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
domain.MongoDBLogger.Info("GetSportMarketPopularity executed successfully",
|
|
zap.Int("result_count", len(popularity)),
|
|
zap.String("query", query),
|
|
zap.Any("args", args),
|
|
)
|
|
|
|
return popularity, nil
|
|
}
|