CrateTransaction+merge conflict fixes

This commit is contained in:
Yared Yemane 2025-05-31 21:34:11 +03:00
commit 49d9dafccb
31 changed files with 1287 additions and 671 deletions

View File

@ -47,6 +47,7 @@ CREATE TABLE IF NOT EXISTS bets (
status INT NOT NULL,
full_name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
company_id BIGINT,
branch_id BIGINT,
user_id BIGINT,
cashed_out BOOLEAN DEFAULT FALSE NOT NULL,
@ -342,7 +343,8 @@ INSERT INTO users (
created_at,
updated_at,
suspended_at,
suspended
suspended,
company_id
)
VALUES (
'Test',
@ -356,7 +358,8 @@ VALUES (
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE
FALSE,
1
);
INSERT INTO users (
first_name,

View File

@ -8,9 +8,10 @@ INSERT INTO bets (
branch_id,
user_id,
is_shop_bet,
cashout_id
cashout_id,
company_id
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *;
-- name: CreateBetOutcome :copyfrom
INSERT INTO bet_outcomes (
@ -45,7 +46,19 @@ VALUES (
);
-- name: GetAllBets :many
SELECT *
FROM bet_with_outcomes;
FROM bet_with_outcomes
wHERE (
branch_id = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
AND (
user_id = $3
OR $3 IS NULL
);
-- name: GetBetByID :one
SELECT *
FROM bet_with_outcomes

View File

@ -42,7 +42,19 @@ VALUES (
RETURNING *;
-- name: GetAllTransactions :many
SELECT *
FROM transactions;
FROM transactions
wHERE (
branch_id = sqlc.narg('branch_id')
OR sqlc.narg('branch_id') IS NULL
)
AND (
company_id = sqlc.narg('company_id')
OR sqlc.narg('company_id') IS NULL
)
AND (
cashier_id = sqlc.narg('cashier_id')
OR sqlc.narg('cashier_id') IS NULL
);
-- name: GetTransactionByID :one
SELECT *
FROM transactions

View File

@ -21,10 +21,11 @@ INSERT INTO bets (
branch_id,
user_id,
is_shop_bet,
cashout_id
cashout_id,
company_id
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet
`
type CreateBetParams struct {
@ -37,6 +38,7 @@ type CreateBetParams struct {
UserID pgtype.Int8 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
CashoutID string `json:"cashout_id"`
CompanyID pgtype.Int8 `json:"company_id"`
}
func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) {
@ -50,6 +52,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
arg.UserID,
arg.IsShopBet,
arg.CashoutID,
arg.CompanyID,
)
var i Bet
err := row.Scan(
@ -59,6 +62,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
@ -107,12 +111,30 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
}
const GetAllBets = `-- name: GetAllBets :many
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
wHERE (
branch_id = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
AND (
user_id = $3
OR $3 IS NULL
)
`
func (q *Queries) GetAllBets(ctx context.Context) ([]BetWithOutcome, error) {
rows, err := q.db.Query(ctx, GetAllBets)
type GetAllBetsParams struct {
BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"`
UserID pgtype.Int8 `json:"user_id"`
}
func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) {
rows, err := q.db.Query(ctx, GetAllBets, arg.BranchID, arg.CompanyID, arg.UserID)
if err != nil {
return nil, err
}
@ -127,6 +149,7 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]BetWithOutcome, error) {
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
@ -147,7 +170,7 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]BetWithOutcome, error) {
}
const GetBetByBranchID = `-- name: GetBetByBranchID :many
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
WHERE branch_id = $1
`
@ -168,6 +191,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
@ -188,7 +212,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
}
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
WHERE cashout_id = $1
`
@ -203,6 +227,7 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
@ -216,7 +241,7 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
}
const GetBetByID = `-- name: GetBetByID :one
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
WHERE id = $1
`
@ -231,6 +256,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
@ -244,7 +270,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
}
const GetBetByUserID = `-- name: GetBetByUserID :many
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
WHERE user_id = $1
`
@ -265,6 +291,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,

View File

@ -62,6 +62,7 @@ type Bet struct {
Status int32 `json:"status"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"`
@ -96,6 +97,7 @@ type BetWithOutcome struct {
Status int32 `json:"status"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"`
@ -383,6 +385,32 @@ type User struct {
ReferredBy pgtype.Text `json:"referred_by"`
}
type UserGameInteraction struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
GameID int64 `json:"game_id"`
InteractionType string `json:"interaction_type"`
Amount pgtype.Numeric `json:"amount"`
DurationSeconds pgtype.Int4 `json:"duration_seconds"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type VirtualGame struct {
ID int64 `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
Category string `json:"category"`
MinBet pgtype.Numeric `json:"min_bet"`
MaxBet pgtype.Numeric `json:"max_bet"`
Volatility string `json:"volatility"`
Rtp pgtype.Numeric `json:"rtp"`
IsFeatured pgtype.Bool `json:"is_featured"`
PopularityScore pgtype.Int4 `json:"popularity_score"`
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameSession struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`

View File

@ -130,10 +130,28 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
const GetAllTransactions = `-- name: GetAllTransactions :many
SELECT id, amount, branch_id, company_id, cashier_id, cashier_name, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, approver_name, branch_location, branch_name, created_at, updated_at
FROM transactions
wHERE (
branch_id = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
AND (
cashier_id = $3
OR $3 IS NULL
)
`
func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) {
rows, err := q.db.Query(ctx, GetAllTransactions)
type GetAllTransactionsParams struct {
BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"`
CashierID pgtype.Int8 `json:"cashier_id"`
}
func (q *Queries) GetAllTransactions(ctx context.Context, arg GetAllTransactionsParams) ([]Transaction, error) {
rows, err := q.db.Query(ctx, GetAllTransactions, arg.BranchID, arg.CompanyID, arg.CashierID)
if err != nil {
return nil, err
}

View File

@ -48,6 +48,7 @@ type Bet struct {
FullName string
PhoneNumber string
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
IsShopBet bool
CashedOut bool
@ -55,6 +56,12 @@ type Bet struct {
CreatedAt time.Time
}
type BetFilter struct {
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
}
type GetBet struct {
ID int64
Amount Currency
@ -63,6 +70,7 @@ type GetBet struct {
FullName string
PhoneNumber string
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
IsShopBet bool
CashedOut bool
@ -77,6 +85,7 @@ type CreateBet struct {
Status OutcomeStatus
FullName string
PhoneNumber string
CompanyID ValidInt64 // Can Be Nullable
BranchID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
IsShopBet bool
@ -164,3 +173,4 @@ func ConvertBet(bet GetBet) BetRes {
CreatedAt: bet.CreatedAt,
}
}

View File

@ -59,3 +59,14 @@ type Response struct {
Success bool `json:"success"`
StatusCode int `json:"status_code"`
}
func CalculateWinnings(amount Currency, totalOdds float32) Currency {
vat := amount.Float32() * 0.15
stakeAfterVat := amount.Float32() - vat
possibleWin := stakeAfterVat * totalOdds
incomeTax := possibleWin * 0.15
return ToCurrency(possibleWin - incomeTax)
}

View File

@ -14,6 +14,13 @@ var SupportedLeagues = []int64{
10047168, // US MLS
10044469, // Ethiopian Premier League
10050282, //UEFA Nations League
10044685, //FIFA Club World Cup
10082328, //Kings League World Cup
10081269, //CONCACAF Champions Cup
10040162, //Asia - World Cup Qualifying
10067624, //South America - World Cup Qualifying
10067913, // Europe - World Cup Qualifying
10067624, // South America - World Cup Qualifying
10043156, //England FA Cup
10042103, //France Cup
@ -22,18 +29,38 @@ var SupportedLeagues = []int64{
10041187, //Kenya Super League
10041315, //Italian Serie A
10041391, //Netherlands Eredivisie
10036538, //Spain Segunda
10041058, //Denmark Superligaen
10077480, //Womens International
10046936, // USA NPSL
10085159, //Baller League UK
10040601, //Argentina Cup
10037440, //Brazil Serie A
10043205, //Copa Sudamericana
10037327, //Austria Landesliga
10082020, //USA USL League One Cup
10037075, //International Match
10046648, //Kenya Cup
10040485, //Kenya Super League
10041369, //Norway Eliteserien
// Basketball
173998768, //NBA
10041830, //NBA
10049984, //WNBA
10037165, //German Bundesliga
10036608, //Italian Lega 1
10040795, //EuroLeague
10036608, //Italian Lega 1
10040795, //EuroLeague
10084178, //Kenya Premier League
10043548, //International Women
// Ice Hockey
10037477, //NHL
10037447, //AHL
10074238, // AIHL
10069385, //IIHF World Championship
// Cricket
}

View File

@ -49,3 +49,76 @@ type IceHockeyOddsResponse struct {
FirstPeriod OddsSection `json:"1st_period"`
Others []OddsSection `json:"others"`
}
type CricketOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
First_Over OddsSection `json:"1st_over"`
First_Innings OddsSection `json:"innings_1"`
Main OddsSection `json:"main"`
Match OddsSection `json:"match"`
Others []OddsSection `json:"others"`
Player OddsSection `json:"player"`
Team OddsSection `json:"team"`
}
type VolleyballOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
Others []OddsSection `json:"others"`
}
type DartsOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
OneEightys OddsSection `json:"180s"`
Extra OddsSection `json:"extra"`
Leg OddsSection `json:"leg"`
Main OddsSection `json:"main"`
Others []OddsSection `json:"others"`
}
type FutsalOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
Score OddsSection `json:"score"`
Others []OddsSection `json:"others"`
}
type AmericanFootballOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
HalfProps OddsSection `json:"half_props"`
Main OddsSection `json:"main"`
QuarterProps OddsSection `json:"quarter_props"`
Others []OddsSection `json:"others"`
}
type RugbyLeagueOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
TenMinute OddsSection `json:"10minute"`
Half OddsSection `json:"half"`
Main OddsSection `json:"main"`
Main2 OddsSection `json:"main_2"`
Others []OddsSection `json:"others"`
Player OddsSection `json:"player"`
Score OddsSection `json:"score"`
Team OddsSection `json:"team"`
}
type RugbyUnionOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Half OddsSection `json:"half"`
Main OddsSection `json:"main"`
Main2 OddsSection `json:"main_2"`
Others []OddsSection `json:"others"`
Player OddsSection `json:"player"`
Score OddsSection `json:"score"`
Team OddsSection `json:"team"`
}
type BaseballOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
MainProps OddsSection `json:"main_props"`
}

View File

@ -290,3 +290,168 @@ type FutsalResultResponse struct {
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// NFLResultResponse represents the structure for NFL game results
type NFLResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstQuarter Score `json:"1"`
SecondQuarter Score `json:"2"`
ThirdQuarter Score `json:"3"`
FourthQuarter Score `json:"4"`
Overtime Score `json:"5"`
TotalScore Score `json:"7"`
} `json:"scores"`
Stats struct {
FirstDowns []string `json:"first_downs"`
TotalYards []string `json:"total_yards"`
PassingYards []string `json:"passing_yards"`
RushingYards []string `json:"rushing_yards"`
Turnovers []string `json:"turnovers"`
TimeOfPossession []string `json:"time_of_possession"`
ThirdDownEfficiency []string `json:"third_down_efficiency"`
FourthDownEfficiency []string `json:"fourth_down_efficiency"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// RugbyResultResponse represents the structure for Rugby game results
type RugbyResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstHalf Score `json:"1"`
SecondHalf Score `json:"2"`
TotalScore Score `json:"7"`
} `json:"scores"`
Stats struct {
Tries []string `json:"tries"`
Conversions []string `json:"conversions"`
Penalties []string `json:"penalties"`
DropGoals []string `json:"drop_goals"`
Possession []string `json:"possession"`
Territory []string `json:"territory"`
Lineouts []string `json:"lineouts"`
Scrums []string `json:"scrums"`
PenaltiesConceded []string `json:"penalties_conceded"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// BaseballResultResponse represents the structure for Baseball game results
type BaseballResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstInning Score `json:"1"`
SecondInning Score `json:"2"`
ThirdInning Score `json:"3"`
FourthInning Score `json:"4"`
FifthInning Score `json:"5"`
SixthInning Score `json:"6"`
SeventhInning Score `json:"7"`
EighthInning Score `json:"8"`
NinthInning Score `json:"9"`
ExtraInnings Score `json:"10"`
TotalScore Score `json:"11"`
} `json:"scores"`
Stats struct {
Hits []string `json:"hits"`
Errors []string `json:"errors"`
LeftOnBase []string `json:"left_on_base"`
Strikeouts []string `json:"strikeouts"`
Walks []string `json:"walks"`
HomeRuns []string `json:"home_runs"`
TotalBases []string `json:"total_bases"`
BattingAverage []string `json:"batting_average"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}

View File

@ -171,32 +171,28 @@ type AmericanFootballMarket int64
const (
// Main
AMERICAN_FOOTBALL_MONEY_LINE AmericanFootballMarket = 170001
AMERICAN_FOOTBALL_SPREAD AmericanFootballMarket = 170002
AMERICAN_FOOTBALL_TOTAL_POINTS AmericanFootballMarket = 170003
AMERICAN_FOOTBALL_GAME_LINES AmericanFootballMarket = 1441
)
type RugbyMarket int64
type RugbyLeagueMarket int64
const (
// Main
RUGBY_MONEY_LINE RugbyMarket = 180001
RUGBY_SPREAD RugbyMarket = 180002
RUGBY_TOTAL_POINTS RugbyMarket = 180003
RUGBY_HANDICAP RugbyMarket = 180004
RUGBY_FIRST_HALF RugbyMarket = 180005
RUGBY_SECOND_HALF RugbyMarket = 180006
RUGBY_L_GAME_BETTING_2_WAY RugbyLeagueMarket = 190006
)
type RugbyUnionMarket int64
const (
// Main
RUGBY_U_GAME_BETTING_2_WAY RugbyLeagueMarket = 80007
)
type BaseballMarket int64
const (
// Main
BASEBALL_MONEY_LINE BaseballMarket = 190001
BASEBALL_SPREAD BaseballMarket = 190002
BASEBALL_TOTAL_RUNS BaseballMarket = 190003
BASEBALL_FIRST_INNING BaseballMarket = 190004
BASEBALL_FIRST_5_INNINGS BaseballMarket = 190005
BASEBALL_GAME_LINES BaseballMarket = 1096
)
// TODO: Move this into the database so that it can be modified dynamically
@ -229,7 +225,7 @@ var SupportedMarkets = map[int64]bool{
int64(BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
int64(BASKETBALL_DOUBLE_RESULT): true,
int64(BASKETBALL_MATCH_RESULT_AND_TOTAL): true,
int64(BASKETBALL_MATCH_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_MATCH_HANDICAP_AND_TOTAL): true,
int64(BASKETBALL_GAME_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_TEAM_TOTALS): true,
int64(BASKETBALL_TEAM_TOTAL_ODD_EVEN): true,
@ -250,7 +246,7 @@ var SupportedMarkets = map[int64]bool{
int64(BASKETBALL_FIRST_HALF_TEAM_TO_SCORE_X_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER): true,
int64(BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL): true,
int64(BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE): true,
int64(BASKETBALL_FIRST_QUARTER_TEAM_TOTALS): true,
int64(BASKETBALL_FIRST_QUARTER_WINNING_MARGIN): false,
@ -259,7 +255,7 @@ var SupportedMarkets = map[int64]bool{
int64(BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER): true,
int64(BASKETBALL_QUARTER_CORRECT_SCORE): false,
int64(BASKETBALL_FIRST_QUARTER_3_WAY_LINES): false,
int64(BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL): false,
int64(BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL): true,
int64(BASKETBALL_FIRST_QUARTER_RACE_TO_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER_BOTH_TEAMS_TO_SCORE_X_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER_TEAM_TO_SCORE_X_POINTS): false,
@ -325,22 +321,14 @@ var SupportedMarkets = map[int64]bool{
int64(FUTSAL_RACE_TO_GOALS): false,
// American Football Markets
int64(AMERICAN_FOOTBALL_MONEY_LINE): true,
int64(AMERICAN_FOOTBALL_SPREAD): true,
int64(AMERICAN_FOOTBALL_TOTAL_POINTS): true,
int64(AMERICAN_FOOTBALL_GAME_LINES): true,
// Rugby Markets
int64(RUGBY_MONEY_LINE): true,
int64(RUGBY_SPREAD): true,
int64(RUGBY_TOTAL_POINTS): true,
int64(RUGBY_HANDICAP): true,
int64(RUGBY_FIRST_HALF): true,
int64(RUGBY_SECOND_HALF): true,
// Rugby League Markets
int64(RUGBY_L_GAME_BETTING_2_WAY): true,
// Ruby Union Markets
int64(RUGBY_U_GAME_BETTING_2_WAY): true,
// Baseball Markets
int64(BASEBALL_MONEY_LINE): true,
int64(BASEBALL_SPREAD): true,
int64(BASEBALL_TOTAL_RUNS): true,
int64(BASEBALL_FIRST_INNING): true,
int64(BASEBALL_FIRST_5_INNINGS): true,
int64(BASEBALL_GAME_LINES): true,
}

View File

@ -1,290 +1,125 @@
package domain
import (
"encoding/json"
"strconv"
"strings"
)
// import (
// "encoding/json"
// "strconv"
// "strings"
// )
// NFLResultResponse represents the structure for NFL game results
type NFLResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstQuarter Score `json:"1"`
SecondQuarter Score `json:"2"`
ThirdQuarter Score `json:"3"`
FourthQuarter Score `json:"4"`
Overtime Score `json:"5"`
TotalScore Score `json:"7"`
} `json:"scores"`
Stats struct {
FirstDowns []string `json:"first_downs"`
TotalYards []string `json:"total_yards"`
PassingYards []string `json:"passing_yards"`
RushingYards []string `json:"rushing_yards"`
Turnovers []string `json:"turnovers"`
TimeOfPossession []string `json:"time_of_possession"`
ThirdDownEfficiency []string `json:"third_down_efficiency"`
FourthDownEfficiency []string `json:"fourth_down_efficiency"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// // ParseNFLResult parses NFL result from raw JSON data
// func ParseNFLResult(data json.RawMessage) (*NFLResultResponse, error) {
// var result NFLResultResponse
// if err := json.Unmarshal(data, &result); err != nil {
// return nil, err
// }
// return &result, nil
// }
// RugbyResultResponse represents the structure for Rugby game results
type RugbyResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstHalf Score `json:"1"`
SecondHalf Score `json:"2"`
TotalScore Score `json:"7"`
} `json:"scores"`
Stats struct {
Tries []string `json:"tries"`
Conversions []string `json:"conversions"`
Penalties []string `json:"penalties"`
DropGoals []string `json:"drop_goals"`
Possession []string `json:"possession"`
Territory []string `json:"territory"`
Lineouts []string `json:"lineouts"`
Scrums []string `json:"scrums"`
PenaltiesConceded []string `json:"penalties_conceded"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// // ParseRugbyResult parses Rugby result from raw JSON data
// func ParseRugbyResult(data json.RawMessage) (*RugbyResultResponse, error) {
// var result RugbyResultResponse
// if err := json.Unmarshal(data, &result); err != nil {
// return nil, err
// }
// return &result, nil
// }
// BaseballResultResponse represents the structure for Baseball game results
type BaseballResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstInning Score `json:"1"`
SecondInning Score `json:"2"`
ThirdInning Score `json:"3"`
FourthInning Score `json:"4"`
FifthInning Score `json:"5"`
SixthInning Score `json:"6"`
SeventhInning Score `json:"7"`
EighthInning Score `json:"8"`
NinthInning Score `json:"9"`
ExtraInnings Score `json:"10"`
TotalScore Score `json:"11"`
} `json:"scores"`
Stats struct {
Hits []string `json:"hits"`
Errors []string `json:"errors"`
LeftOnBase []string `json:"left_on_base"`
Strikeouts []string `json:"strikeouts"`
Walks []string `json:"walks"`
HomeRuns []string `json:"home_runs"`
TotalBases []string `json:"total_bases"`
BattingAverage []string `json:"batting_average"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
StadiumData map[string]string `json:"stadium_data"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
// // ParseRugbyUnionResult parses Rugby Union result from raw JSON data
// func ParseRugbyUnionResult(data json.RawMessage) (*RugbyResultResponse, error) {
// return ParseRugbyResult(data)
// }
// ParseNFLResult parses NFL result from raw JSON data
func ParseNFLResult(data json.RawMessage) (*NFLResultResponse, error) {
var result NFLResultResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
// // ParseRugbyLeagueResult parses Rugby League result from raw JSON data
// func ParseRugbyLeagueResult(data json.RawMessage) (*RugbyResultResponse, error) {
// return ParseRugbyResult(data)
// }
// ParseRugbyResult parses Rugby result from raw JSON data
func ParseRugbyResult(data json.RawMessage) (*RugbyResultResponse, error) {
var result RugbyResultResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
// // ParseBaseballResult parses Baseball result from raw JSON data
// func ParseBaseballResult(data json.RawMessage) (*BaseballResultResponse, error) {
// var result BaseballResultResponse
// if err := json.Unmarshal(data, &result); err != nil {
// return nil, err
// }
// return &result, nil
// }
// ParseRugbyUnionResult parses Rugby Union result from raw JSON data
func ParseRugbyUnionResult(data json.RawMessage) (*RugbyResultResponse, error) {
return ParseRugbyResult(data)
}
// // GetNFLWinner determines the winner of an NFL game
// func GetNFLWinner(result *NFLResultResponse) (string, error) {
// homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
// if err != nil {
// return "", err
// }
// awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
// if err != nil {
// return "", err
// }
// ParseRugbyLeagueResult parses Rugby League result from raw JSON data
func ParseRugbyLeagueResult(data json.RawMessage) (*RugbyResultResponse, error) {
return ParseRugbyResult(data)
}
// if homeScore > awayScore {
// return result.Home.Name, nil
// } else if awayScore > homeScore {
// return result.Away.Name, nil
// }
// return "Draw", nil
// }
// ParseBaseballResult parses Baseball result from raw JSON data
func ParseBaseballResult(data json.RawMessage) (*BaseballResultResponse, error) {
var result BaseballResultResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
// // GetRugbyWinner determines the winner of a Rugby game
// func GetRugbyWinner(result *RugbyResultResponse) (string, error) {
// homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
// if err != nil {
// return "", err
// }
// awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
// if err != nil {
// return "", err
// }
// GetNFLWinner determines the winner of an NFL game
func GetNFLWinner(result *NFLResultResponse) (string, error) {
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
if err != nil {
return "", err
}
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
if err != nil {
return "", err
}
// if homeScore > awayScore {
// return result.Home.Name, nil
// } else if awayScore > homeScore {
// return result.Away.Name, nil
// }
// return "Draw", nil
// }
if homeScore > awayScore {
return result.Home.Name, nil
} else if awayScore > homeScore {
return result.Away.Name, nil
}
return "Draw", nil
}
// // GetBaseballWinner determines the winner of a Baseball game
// func GetBaseballWinner(result *BaseballResultResponse) (string, error) {
// homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
// if err != nil {
// return "", err
// }
// awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
// if err != nil {
// return "", err
// }
// GetRugbyWinner determines the winner of a Rugby game
func GetRugbyWinner(result *RugbyResultResponse) (string, error) {
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
if err != nil {
return "", err
}
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
if err != nil {
return "", err
}
// if homeScore > awayScore {
// return result.Home.Name, nil
// } else if awayScore > homeScore {
// return result.Away.Name, nil
// }
// return "Draw", nil
// }
if homeScore > awayScore {
return result.Home.Name, nil
} else if awayScore > homeScore {
return result.Away.Name, nil
}
return "Draw", nil
}
// // FormatNFLScore formats the NFL score in a readable format
// func FormatNFLScore(result *NFLResultResponse) string {
// return strings.Join([]string{
// result.Home.Name + " " + result.Scores.TotalScore.Home,
// result.Away.Name + " " + result.Scores.TotalScore.Away,
// }, " - ")
// }
// GetBaseballWinner determines the winner of a Baseball game
func GetBaseballWinner(result *BaseballResultResponse) (string, error) {
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
if err != nil {
return "", err
}
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
if err != nil {
return "", err
}
// // FormatRugbyScore formats the Rugby score in a readable format
// func FormatRugbyScore(result *RugbyResultResponse) string {
// return strings.Join([]string{
// result.Home.Name + " " + result.Scores.TotalScore.Home,
// result.Away.Name + " " + result.Scores.TotalScore.Away,
// }, " - ")
// }
if homeScore > awayScore {
return result.Home.Name, nil
} else if awayScore > homeScore {
return result.Away.Name, nil
}
return "Draw", nil
}
// FormatNFLScore formats the NFL score in a readable format
func FormatNFLScore(result *NFLResultResponse) string {
return strings.Join([]string{
result.Home.Name + " " + result.Scores.TotalScore.Home,
result.Away.Name + " " + result.Scores.TotalScore.Away,
}, " - ")
}
// FormatRugbyScore formats the Rugby score in a readable format
func FormatRugbyScore(result *RugbyResultResponse) string {
return strings.Join([]string{
result.Home.Name + " " + result.Scores.TotalScore.Home,
result.Away.Name + " " + result.Scores.TotalScore.Away,
}, " - ")
}
// FormatBaseballScore formats the Baseball score in a readable format
func FormatBaseballScore(result *BaseballResultResponse) string {
return strings.Join([]string{
result.Home.Name + " " + result.Scores.TotalScore.Home,
result.Away.Name + " " + result.Scores.TotalScore.Away,
}, " - ")
}
// // FormatBaseballScore formats the Baseball score in a readable format
// func FormatBaseballScore(result *BaseballResultResponse) string {
// return strings.Join([]string{
// result.Home.Name + " " + result.Scores.TotalScore.Home,
// result.Away.Name + " " + result.Scores.TotalScore.Away,
// }, " - ")
// }

View File

@ -47,6 +47,12 @@ type Transaction struct {
UpdatedAt time.Time
CreatedAt time.Time
}
type TransactionFilter struct {
CompanyID ValidInt64
BranchID ValidInt64
CashierID ValidInt64
}
type CreateTransaction struct {
Amount Currency
BranchID int64

View File

@ -22,6 +22,10 @@ func convertDBBet(bet dbgen.Bet) domain.Bet {
Value: bet.BranchID.Int64,
Valid: bet.BranchID.Valid,
},
CompanyID: domain.ValidInt64{
Value: bet.CompanyID.Int64,
Valid: bet.CompanyID.Valid,
},
UserID: domain.ValidInt64{
Value: bet.UserID.Int64,
Valid: bet.UserID.Valid,
@ -111,6 +115,7 @@ func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams {
Status: int32(bet.Status),
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: pgtype.Int8{
Int64: bet.BranchID.Value,
Valid: bet.BranchID.Valid,
@ -168,8 +173,21 @@ func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet
return convertDBBetWithOutcomes(bet), nil
}
func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
bets, err := s.queries.GetAllBets(ctx)
func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) {
bets, err := s.queries.GetAllBets(ctx, dbgen.GetAllBetsParams{
BranchID: pgtype.Int8{
Int64: filter.BranchID.Value,
Valid: filter.BranchID.Valid,
},
CompanyID: pgtype.Int8{
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
UserID: pgtype.Int8{
Int64: filter.UserID.Value,
Valid: filter.UserID.Valid,
},
})
if err != nil {
return nil, err
}

View File

@ -84,8 +84,21 @@ func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transa
return convertDBTransaction(transaction), nil
}
func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) {
transaction, err := s.queries.GetAllTransactions(ctx)
func (s *Store) GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error) {
transaction, err := s.queries.GetAllTransactions(ctx, dbgen.GetAllTransactionsParams{
BranchID: pgtype.Int8{
Int64: filter.BranchID.Value,
Valid: filter.BranchID.Valid,
},
CompanyID: pgtype.Int8{
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
CashierID: pgtype.Int8{
Int64: filter.CashierID.Value,
Valid: filter.CashierID.Valid,
},
})
if err != nil {
return nil, err

View File

@ -11,7 +11,7 @@ type BetStore interface {
CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error)
GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error)
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)

View File

@ -197,6 +197,11 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
Value: branch.ID,
Valid: true,
}
newBet.CompanyID = domain.ValidInt64{
Value: branch.CompanyID,
Valid: true,
}
newBet.UserID = domain.ValidInt64{
Value: userID,
Valid: true,
@ -227,6 +232,10 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
Value: branch.ID,
Valid: true,
}
newBet.CompanyID = domain.ValidInt64{
Value: branch.CompanyID,
Valid: true,
}
newBet.UserID = domain.ValidInt64{
Value: userID,
Valid: true,
@ -483,8 +492,8 @@ func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, erro
func (s *Service) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
return s.betStore.GetBetByCashoutID(ctx, id)
}
func (s *Service) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
return s.betStore.GetAllBets(ctx)
func (s *Service) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) {
return s.betStore.GetAllBets(ctx, filter)
}
func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domain.GetBet, error) {
@ -500,6 +509,41 @@ 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 {
bet, err := s.GetBetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to update bet status. Invalid bet id")
return err
}
if bet.IsShopBet ||
status == domain.OUTCOME_STATUS_ERROR ||
status == domain.OUTCOME_STATUS_PENDING ||
status == domain.OUTCOME_STATUS_LOSS {
return s.betStore.UpdateStatus(ctx, id, status)
}
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id)
if err != nil {
s.logger.Error("Failed to update bet status. Invalid customer wallet id")
return err
}
var amount domain.Currency
if status == domain.OUTCOME_STATUS_WIN {
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds)
} else if status == domain.OUTCOME_STATUS_HALF {
amount = (domain.CalculateWinnings(bet.Amount, bet.TotalOdds)) / 2
} else {
amount = bet.Amount
}
err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount)
if err != nil {
s.logger.Error("Failed to update bet status. Failed to update user wallet")
return err
}
return s.betStore.UpdateStatus(ctx, id, status)
}

View File

@ -7,7 +7,6 @@ import (
"io"
"log"
"net/http"
"slices"
"strconv"
"sync"
"time"
@ -189,8 +188,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
source string
}{
{"https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", "bet365"},
{"https://api.b365api.com/v1/betfair/sb/upcoming?sport_id=%d&token=%s&page=%d", "betfair"},
{"https://api.b365api.com/v1/1xbet/upcoming?sport_id=%d&token=%s&page=%d", "1xbet"},
// {"https://api.b365api.com/v1/betfair/sb/upcoming?sport_id=%d&token=%s&page=%d", "betfair"},
// {"https://api.b365api.com/v1/1xbet/upcoming?sport_id=%d&token=%s&page=%d", "1xbet"},
}
for _, url := range urls {
@ -207,16 +206,23 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
}
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
sportIDs := []int{1, 18, 17}
var totalPages int = 1
var page int = 0
var limit int = 100
var count int = 0
sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91}
// TODO: Add the league skipping again when we have dynamic leagues
// b, err := os.OpenFile("logs/skipped_leagues.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// if err != nil {
// log.Printf("❌ Failed to open leagues file %v", err)
// return
// }
for _, sportID := range sportIDs {
var totalPages int = 1
var page int = 0
var limit int = 100
var count int = 0
log.Printf("Sport ID %d", sportID)
for page <= totalPages {
page = page + 1
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
log.Printf("📡 Fetching data for event data page %d", page)
log.Printf("📡 Fetching data for sport %d at page %d", sportID, page)
resp, err := http.Get(url)
if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
@ -240,17 +246,21 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
// continue
// }
leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
if err != nil {
log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
continue
}
// leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
// if err != nil {
// log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
// continue
// }
if !slices.Contains(domain.SupportedLeagues, leagueID) {
// fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
skippedLeague = append(skippedLeague, ev.League.Name)
continue
}
// if !slices.Contains(domain.SupportedLeagues, leagueID) {
// // fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
// _, err = fmt.Fprintf(b, "Skipped league %s (%d) in sport %d\n", ev.League.Name, leagueID, sportID)
// if err != nil {
// fmt.Printf(" Error while logging skipped league")
// }
// skippedLeague = append(skippedLeague, ev.League.Name)
// continue
// }
event := domain.UpcomingEvent{
ID: ev.ID,
@ -282,7 +292,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
}
log.Printf("⚠️ Skipped leagues %v", len(skippedLeague))
// log.Printf("⚠️ Total pages %v", data.Pager.Total)
log.Printf("⚠️ Total pages %v", data.Pager.Total/data.Pager.PerPage)
totalPages = data.Pager.Total / data.Pager.PerPage
if count >= limit {

View File

@ -98,6 +98,46 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
s.logger.Error("Error while inserting ice hockey odd")
errs = append(errs, err)
}
case domain.CRICKET:
if err := s.parseCricket(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting cricket odd")
errs = append(errs, err)
}
case domain.VOLLEYBALL:
if err := s.parseVolleyball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting volleyball odd")
errs = append(errs, err)
}
case domain.DARTS:
if err := s.parseDarts(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting darts odd")
errs = append(errs, err)
}
case domain.FUTSAL:
if err := s.parseFutsal(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting futsal odd")
errs = append(errs, err)
}
case domain.AMERICAN_FOOTBALL:
if err := s.parseAmericanFootball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting american football odd")
errs = append(errs, err)
}
case domain.RUGBY_LEAGUE:
if err := s.parseRugbyLeague(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting rugby league odd")
errs = append(errs, err)
}
case domain.RUGBY_UNION:
if err := s.parseRugbyUnion(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting rugby union odd")
errs = append(errs, err)
}
case domain.BASEBALL:
if err := s.parseBaseball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting baseball odd")
errs = append(errs, err)
}
}
// result := oddsData.Results[0]
@ -223,6 +263,332 @@ func (s *ServiceImpl) parseIceHockey(ctx context.Context, res json.RawMessage) e
return nil
}
func (s *ServiceImpl) parseCricket(ctx context.Context, res json.RawMessage) error {
var cricketRes domain.CricketOddsResponse
if err := json.Unmarshal(res, &cricketRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if cricketRes.EventID == "" && cricketRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"1st_over": cricketRes.Main,
"innings_1": cricketRes.First_Innings,
"main": cricketRes.Main,
"match": cricketRes.Match,
"player": cricketRes.Player,
"team": cricketRes.Team,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range cricketRes.Others {
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseVolleyball(ctx context.Context, res json.RawMessage) error {
var volleyballRes domain.VolleyballOddsResponse
if err := json.Unmarshal(res, &volleyballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if volleyballRes.EventID == "" && volleyballRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"main": volleyballRes.Main,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range volleyballRes.Others {
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseDarts(ctx context.Context, res json.RawMessage) error {
var dartsRes domain.DartsOddsResponse
if err := json.Unmarshal(res, &dartsRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if dartsRes.EventID == "" && dartsRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"180s": dartsRes.OneEightys,
"extra": dartsRes.Extra,
"leg": dartsRes.Leg,
"main": dartsRes.Main,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range dartsRes.Others {
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseFutsal(ctx context.Context, res json.RawMessage) error {
var futsalRes domain.FutsalOddsResponse
if err := json.Unmarshal(res, &futsalRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if futsalRes.EventID == "" && futsalRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"main": futsalRes.Main,
"score": futsalRes.Score,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range futsalRes.Others {
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseAmericanFootball(ctx context.Context, res json.RawMessage) error {
var americanFootballRes domain.AmericanFootballOddsResponse
if err := json.Unmarshal(res, &americanFootballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if americanFootballRes.EventID == "" && americanFootballRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"half_props": americanFootballRes.HalfProps,
"main": americanFootballRes.Main,
"quarter_props": americanFootballRes.QuarterProps,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range americanFootballRes.Others {
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseRugbyLeague(ctx context.Context, res json.RawMessage) error {
var rugbyLeagueRes domain.RugbyLeagueOddsResponse
if err := json.Unmarshal(res, &rugbyLeagueRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if rugbyLeagueRes.EventID == "" && rugbyLeagueRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"10minute": rugbyLeagueRes.TenMinute,
"main": rugbyLeagueRes.Main,
"main_2": rugbyLeagueRes.Main2,
"player": rugbyLeagueRes.Player,
"Score": rugbyLeagueRes.Score,
"Team": rugbyLeagueRes.Team,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range rugbyLeagueRes.Others {
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseRugbyUnion(ctx context.Context, res json.RawMessage) error {
var rugbyUnionRes domain.RugbyUnionOddsResponse
if err := json.Unmarshal(res, &rugbyUnionRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if rugbyUnionRes.EventID == "" && rugbyUnionRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"main": rugbyUnionRes.Main,
"main_2": rugbyUnionRes.Main2,
"player": rugbyUnionRes.Player,
"Score": rugbyUnionRes.Score,
"Team": rugbyUnionRes.Team,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range rugbyUnionRes.Others {
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseBaseball(ctx context.Context, res json.RawMessage) error {
var baseballRes domain.BaseballOddsResponse
if err := json.Unmarshal(res, &baseballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err
}
if baseballRes.EventID == "" && baseballRes.FI == "" {
s.logger.Error("Skipping result with no valid Event ID")
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"main": baseballRes.Main,
"mani_props": baseballRes.MainProps,
}
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, baseballRes.EventID, baseballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error {
if len(section.Sp) == 0 {
return nil

View File

@ -429,7 +429,7 @@ func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }
case "Money Line":
return evaluateMoneyLine(outcome, score)
case "Spread", "Line":
case "Spread", "Line", "Run Line":
// Since Spread betting is essentially the same thing
return evaluateAsianHandicap(outcome, score)
case "Total":
@ -985,36 +985,6 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
finalScore := parseSS(result.SS)
switch outcome.MarketName {
case "Money Line":
return evaluateRugbyMoneyLine(outcome, finalScore)
case "Spread":
return evaluateRugbySpread(outcome, finalScore)
case "Total Points":
return evaluateRugbyTotalPoints(outcome, finalScore)
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
}
}
func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
finalScore := parseSS(result.SS)
switch outcome.MarketName {
case "Money Line":
return evaluateBaseballMoneyLine(outcome, finalScore)
case "Spread":
return evaluateBaseballSpread(outcome, finalScore)
case "Total Runs":
return evaluateBaseballTotalRuns(outcome, finalScore)
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
}
}
func evaluateVolleyballGamelines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddName {
case "Total":
@ -1023,3 +993,16 @@ func evaluateVolleyballGamelines(outcome domain.BetOutcome, score struct{ Home,
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
}
}
func evaluateGameBettingTwoWay(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddName {
case "Handicap":
return evaluateAsianHandicap(outcome, score)
case "Total":
return evaluateTotalOverUnder(outcome, score)
case "To Win":
return evaluateFullTimeResult(outcome, score)
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
}
}

View File

@ -161,71 +161,6 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
return nil
}
// func (s *Service) FetchAndStoreResult(ctx context.Context, eventID string) error {
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.config.Bet365Token, eventID)
// res, err := s.client.Get(url)
// if err != nil {
// s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
// return fmt.Errorf("failed to fetch result: %w", err)
// }
// defer res.Body.Close()
// if res.StatusCode != http.StatusOK {
// s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", res.StatusCode)
// return fmt.Errorf("unexpected status code: %d", res.StatusCode)
// }
// var apiResp domain.BaseResultResponse
// if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
// s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
// return fmt.Errorf("failed to decode result: %w", err)
// }
// if apiResp.Success != 1 || len(apiResp.Results) == 0 {
// s.logger.Error("Invalid API response", "event_id", eventID)
// return fmt.Errorf("no result returned from API")
// }
// r := apiResp.Results[0]
// if r.TimeStatus != "3" {
// s.logger.Warn("Match not yet completed", "event_id", eventID)
// return fmt.Errorf("match not yet completed")
// }
// eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
// if err != nil {
// s.logger.Error("Failed to parse event_id", "event_id", eventID, "error", err)
// return fmt.Errorf("failed to parse event_id: %w", err)
// }
// halfScore := ""
// if r.Scores.FirstHalf.Home != "" {
// halfScore = fmt.Sprintf("%s-%s", r.Scores.FirstHalf.Home, r.Scores.FirstHalf.Away)
// }
// result := domain.Result{
// EventID: eventIDInt,
// Status: domain.OUTCOME_STATUS_PENDING,
// Score: r.SS,
// FullTimeScore: r.SS,
// HalfTimeScore: halfScore,
// SS: r.SS,
// Scores: make(map[string]domain.Score),
// }
// for k, v := range map[string]domain.Score{
// "1": r.Scores.FirstHalf,
// "2": r.Scores.SecondHalf,
// } {
// result.Scores[k] = domain.Score{
// Home: v.Home,
// Away: v.Away,
// }
// }
// return s.repo.InsertResult(ctx, result)
// }
func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, sportID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
url := fmt.Sprintf("https://api.b365api.com/v1/event/view?token=%s&event_id=%d", s.config.Bet365Token, eventID)
@ -598,21 +533,7 @@ func (s *Service) parseNFL(resultRes json.RawMessage, eventID, oddID, marketID i
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
finalScore := parseSS(nflResp.SS)
var status domain.OutcomeStatus
var err error
switch outcome.MarketName {
case "Money Line":
status, err = evaluateNFLMoneyLine(outcome, finalScore)
case "Spread":
status, err = evaluateNFLSpread(outcome, finalScore)
case "Total Points":
status, err = evaluateNFLTotalPoints(outcome, finalScore)
default:
return domain.CreateResult{}, fmt.Errorf("unsupported market: %s", outcome.MarketName)
}
status, err := s.evaluateNFLOutcome(outcome, nflResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
@ -639,7 +560,7 @@ func (s *Service) parseRugbyUnion(resultRes json.RawMessage, eventID, oddID, mar
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := evaluateRugbyOutcome(outcome, &rugbyResp)
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
@ -664,7 +585,7 @@ func (s *Service) parseRugbyLeague(resultRes json.RawMessage, eventID, oddID, ma
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := evaluateRugbyOutcome(outcome, &rugbyResp)
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
@ -689,7 +610,7 @@ func (s *Service) parseBaseball(resultRes json.RawMessage, eventID, oddID, marke
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := evaluateBaseballOutcome(outcome, &baseballResp)
status, err := s.evaluateBaseballOutcome(outcome, baseballResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
@ -986,21 +907,51 @@ func (s *Service) evaluateFutsalOutcome(outcome domain.BetOutcome, res domain.Fu
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) EvaluateNFLOutcome(outcome domain.BetOutcome, finalScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, res domain.NFLResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
finalScore := parseSS(res.SS)
switch outcome.MarketID {
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
return evaluateNFLMoneyLine(outcome, finalScore)
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
return evaluateNFLSpread(outcome, finalScore)
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
return evaluateNFLTotalPoints(outcome, finalScore)
case int64(domain.AMERICAN_FOOTBALL_GAME_LINES):
return evaluateGameLines(outcome, finalScore)
default:
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
}
}
func (s *Service) evaluateRugbyOutcome(outcome domain.BetOutcome, result domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
finalScore := parseSS(result.SS)
switch outcome.MarketID {
case int64(domain.RUGBY_L_GAME_BETTING_2_WAY):
return evaluateGameBettingTwoWay(outcome, finalScore)
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
}
}
func (s *Service) evaluateBaseballOutcome(outcome domain.BetOutcome, res domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
finalScore := parseSS(res.SS)
switch outcome.MarketID {
case int64(domain.BASEBALL_GAME_LINES):
return evaluateGameLines(outcome, finalScore)
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
}
}

View File

@ -1,189 +1,189 @@
package services
import (
"encoding/json"
"fmt"
"time"
// import (
// "encoding/json"
// "fmt"
// "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
// )
// ResultCheckerService handles the checking of game results
type ResultCheckerService struct {
// Add any dependencies here (e.g., repositories, external APIs)
}
// // ResultCheckerService handles the checking of game results
// type ResultCheckerService struct {
// // Add any dependencies here (e.g., repositories, external APIs)
// }
// NewResultCheckerService creates a new instance of ResultCheckerService
func NewResultCheckerService() *ResultCheckerService {
return &ResultCheckerService{}
}
// // NewResultCheckerService creates a new instance of ResultCheckerService
// func NewResultCheckerService() *ResultCheckerService {
// return &ResultCheckerService{}
// }
// CheckNFLResult checks the result of an NFL game
func (s *ResultCheckerService) CheckNFLResult(data json.RawMessage) (*domain.Result, error) {
nflResult, err := domain.ParseNFLResult(data)
if err != nil {
return nil, fmt.Errorf("failed to parse NFL result: %w", err)
}
// // CheckNFLResult checks the result of an NFL game
// func (s *ResultCheckerService) CheckNFLResult(data json.RawMessage) (*domain.Result, error) {
// nflResult, err := domain.ParseNFLResult(data)
// if err != nil {
// return nil, fmt.Errorf("failed to parse NFL result: %w", err)
// }
winner, err := domain.GetNFLWinner(nflResult)
if err != nil {
return nil, fmt.Errorf("failed to determine NFL winner: %w", err)
}
// winner, err := domain.GetNFLWinner(nflResult)
// if err != nil {
// return nil, fmt.Errorf("failed to determine NFL winner: %w", err)
// }
score := domain.FormatNFLScore(nflResult)
// score := domain.FormatNFLScore(nflResult)
return &domain.Result{
Status: determineOutcomeStatus(winner, nflResult.Home.Name, nflResult.Away.Name),
Score: score,
FullTimeScore: score,
SS: nflResult.SS,
Scores: map[string]domain.Score{
"1": nflResult.Scores.FirstQuarter,
"2": nflResult.Scores.SecondQuarter,
"3": nflResult.Scores.ThirdQuarter,
"4": nflResult.Scores.FourthQuarter,
"5": nflResult.Scores.Overtime,
"7": nflResult.Scores.TotalScore,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
// return &domain.Result{
// Status: determineOutcomeStatus(winner, nflResult.Home.Name, nflResult.Away.Name),
// Score: score,
// FullTimeScore: score,
// SS: nflResult.SS,
// Scores: map[string]domain.Score{
// "1": nflResult.Scores.FirstQuarter,
// "2": nflResult.Scores.SecondQuarter,
// "3": nflResult.Scores.ThirdQuarter,
// "4": nflResult.Scores.FourthQuarter,
// "5": nflResult.Scores.Overtime,
// "7": nflResult.Scores.TotalScore,
// },
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }, nil
// }
// determineOutcomeStatus determines the outcome status based on the winner and teams
func determineOutcomeStatus(winner, homeTeam, awayTeam string) domain.OutcomeStatus {
if winner == "Draw" {
return domain.OUTCOME_STATUS_VOID
}
if winner == homeTeam {
return domain.OUTCOME_STATUS_WIN
}
if winner == awayTeam {
return domain.OUTCOME_STATUS_LOSS
}
return domain.OUTCOME_STATUS_PENDING
}
// // determineOutcomeStatus determines the outcome status based on the winner and teams
// func determineOutcomeStatus(winner, homeTeam, awayTeam string) domain.OutcomeStatus {
// if winner == "Draw" {
// return domain.OUTCOME_STATUS_VOID
// }
// if winner == homeTeam {
// return domain.OUTCOME_STATUS_WIN
// }
// if winner == awayTeam {
// return domain.OUTCOME_STATUS_LOSS
// }
// return domain.OUTCOME_STATUS_PENDING
// }
// CheckRugbyResult checks the result of a Rugby game
func (s *ResultCheckerService) CheckRugbyResult(data json.RawMessage) (*domain.Result, error) {
rugbyResult, err := domain.ParseRugbyResult(data)
if err != nil {
return nil, fmt.Errorf("failed to parse Rugby result: %w", err)
}
// // CheckRugbyResult checks the result of a Rugby game
// func (s *ResultCheckerService) CheckRugbyResult(data json.RawMessage) (*domain.Result, error) {
// rugbyResult, err := domain.ParseRugbyResult(data)
// if err != nil {
// return nil, fmt.Errorf("failed to parse Rugby result: %w", err)
// }
winner, err := domain.GetRugbyWinner(rugbyResult)
if err != nil {
return nil, fmt.Errorf("failed to determine Rugby winner: %w", err)
}
// winner, err := domain.GetRugbyWinner(rugbyResult)
// if err != nil {
// return nil, fmt.Errorf("failed to determine Rugby winner: %w", err)
// }
score := domain.FormatRugbyScore(rugbyResult)
// score := domain.FormatRugbyScore(rugbyResult)
return &domain.Result{
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
Score: score,
FullTimeScore: score,
SS: rugbyResult.SS,
Scores: map[string]domain.Score{
"1": rugbyResult.Scores.FirstHalf,
"2": rugbyResult.Scores.SecondHalf,
"7": rugbyResult.Scores.TotalScore,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
// return &domain.Result{
// Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
// Score: score,
// FullTimeScore: score,
// SS: rugbyResult.SS,
// Scores: map[string]domain.Score{
// "1": rugbyResult.Scores.FirstHalf,
// "2": rugbyResult.Scores.SecondHalf,
// "7": rugbyResult.Scores.TotalScore,
// },
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }, nil
// }
// CheckBaseballResult checks the result of a Baseball game
func (s *ResultCheckerService) CheckBaseballResult(data json.RawMessage) (*domain.Result, error) {
baseballResult, err := domain.ParseBaseballResult(data)
if err != nil {
return nil, fmt.Errorf("failed to parse Baseball result: %w", err)
}
// // CheckBaseballResult checks the result of a Baseball game
// func (s *ResultCheckerService) CheckBaseballResult(data json.RawMessage) (*domain.Result, error) {
// baseballResult, err := domain.ParseBaseballResult(data)
// if err != nil {
// return nil, fmt.Errorf("failed to parse Baseball result: %w", err)
// }
winner, err := domain.GetBaseballWinner(baseballResult)
if err != nil {
return nil, fmt.Errorf("failed to determine Baseball winner: %w", err)
}
// winner, err := domain.GetBaseballWinner(baseballResult)
// if err != nil {
// return nil, fmt.Errorf("failed to determine Baseball winner: %w", err)
// }
score := domain.FormatBaseballScore(baseballResult)
// score := domain.FormatBaseballScore(baseballResult)
return &domain.Result{
Status: determineOutcomeStatus(winner, baseballResult.Home.Name, baseballResult.Away.Name),
Score: score,
FullTimeScore: score,
SS: baseballResult.SS,
Scores: map[string]domain.Score{
"1": baseballResult.Scores.FirstInning,
"2": baseballResult.Scores.SecondInning,
"3": baseballResult.Scores.ThirdInning,
"4": baseballResult.Scores.FourthInning,
"5": baseballResult.Scores.FifthInning,
"6": baseballResult.Scores.SixthInning,
"7": baseballResult.Scores.SeventhInning,
"8": baseballResult.Scores.EighthInning,
"9": baseballResult.Scores.NinthInning,
"10": baseballResult.Scores.ExtraInnings,
"T": baseballResult.Scores.TotalScore,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
// return &domain.Result{
// Status: determineOutcomeStatus(winner, baseballResult.Home.Name, baseballResult.Away.Name),
// Score: score,
// FullTimeScore: score,
// SS: baseballResult.SS,
// Scores: map[string]domain.Score{
// "1": baseballResult.Scores.FirstInning,
// "2": baseballResult.Scores.SecondInning,
// "3": baseballResult.Scores.ThirdInning,
// "4": baseballResult.Scores.FourthInning,
// "5": baseballResult.Scores.FifthInning,
// "6": baseballResult.Scores.SixthInning,
// "7": baseballResult.Scores.SeventhInning,
// "8": baseballResult.Scores.EighthInning,
// "9": baseballResult.Scores.NinthInning,
// "10": baseballResult.Scores.ExtraInnings,
// "T": baseballResult.Scores.TotalScore,
// },
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }, nil
// }
// CheckRugbyUnionResult checks the result of a Rugby Union game
func (s *ResultCheckerService) CheckRugbyUnionResult(data json.RawMessage) (*domain.Result, error) {
rugbyResult, err := domain.ParseRugbyUnionResult(data)
if err != nil {
return nil, fmt.Errorf("failed to parse Rugby Union result: %w", err)
}
// // CheckRugbyUnionResult checks the result of a Rugby Union game
// func (s *ResultCheckerService) CheckRugbyUnionResult(data json.RawMessage) (*domain.Result, error) {
// rugbyResult, err := domain.ParseRugbyUnionResult(data)
// if err != nil {
// return nil, fmt.Errorf("failed to parse Rugby Union result: %w", err)
// }
winner, err := domain.GetRugbyWinner(rugbyResult)
if err != nil {
return nil, fmt.Errorf("failed to determine Rugby Union winner: %w", err)
}
// winner, err := domain.GetRugbyWinner(rugbyResult)
// if err != nil {
// return nil, fmt.Errorf("failed to determine Rugby Union winner: %w", err)
// }
score := domain.FormatRugbyScore(rugbyResult)
// score := domain.FormatRugbyScore(rugbyResult)
return &domain.Result{
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
Score: score,
FullTimeScore: score,
SS: rugbyResult.SS,
Scores: map[string]domain.Score{
"1": rugbyResult.Scores.FirstHalf,
"2": rugbyResult.Scores.SecondHalf,
"7": rugbyResult.Scores.TotalScore,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
// return &domain.Result{
// Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
// Score: score,
// FullTimeScore: score,
// SS: rugbyResult.SS,
// Scores: map[string]domain.Score{
// "1": rugbyResult.Scores.FirstHalf,
// "2": rugbyResult.Scores.SecondHalf,
// "7": rugbyResult.Scores.TotalScore,
// },
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }, nil
// }
// CheckRugbyLeagueResult checks the result of a Rugby League game
func (s *ResultCheckerService) CheckRugbyLeagueResult(data json.RawMessage) (*domain.Result, error) {
rugbyResult, err := domain.ParseRugbyLeagueResult(data)
if err != nil {
return nil, fmt.Errorf("failed to parse Rugby League result: %w", err)
}
// // CheckRugbyLeagueResult checks the result of a Rugby League game
// func (s *ResultCheckerService) CheckRugbyLeagueResult(data json.RawMessage) (*domain.Result, error) {
// rugbyResult, err := domain.ParseRugbyLeagueResult(data)
// if err != nil {
// return nil, fmt.Errorf("failed to parse Rugby League result: %w", err)
// }
winner, err := domain.GetRugbyWinner(rugbyResult)
if err != nil {
return nil, fmt.Errorf("failed to determine Rugby League winner: %w", err)
}
// winner, err := domain.GetRugbyWinner(rugbyResult)
// if err != nil {
// return nil, fmt.Errorf("failed to determine Rugby League winner: %w", err)
// }
score := domain.FormatRugbyScore(rugbyResult)
// score := domain.FormatRugbyScore(rugbyResult)
return &domain.Result{
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
Score: score,
FullTimeScore: score,
SS: rugbyResult.SS,
Scores: map[string]domain.Score{
"1": rugbyResult.Scores.FirstHalf,
"2": rugbyResult.Scores.SecondHalf,
"7": rugbyResult.Scores.TotalScore,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
// return &domain.Result{
// Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
// Score: score,
// FullTimeScore: score,
// SS: rugbyResult.SS,
// Scores: map[string]domain.Score{
// "1": rugbyResult.Scores.FirstHalf,
// "2": rugbyResult.Scores.SecondHalf,
// "7": rugbyResult.Scores.TotalScore,
// },
// CreatedAt: time.Now(),
// UpdatedAt: time.Now(),
// }, nil
// }

View File

@ -9,7 +9,7 @@ import (
type TransactionStore interface {
CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error)
GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error)
GetAllTransactions(ctx context.Context) ([]domain.Transaction, error)
GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error)
GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error)
UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error
}

View File

@ -22,8 +22,8 @@ func (s *Service) CreateTransaction(ctx context.Context, transaction domain.Crea
func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) {
return s.transactionStore.GetTransactionByID(ctx, id)
}
func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) {
return s.transactionStore.GetAllTransactions(ctx)
func (s *Service) GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error) {
return s.transactionStore.GetAllTransactions(ctx, filter)
}
func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) {
return s.transactionStore.GetTransactionByBranch(ctx, id)

View File

@ -150,7 +150,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse
// @Router /bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
bets, err := h.betSvc.GetAllBets(c.Context())
companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64)
bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
BranchID: branchID,
CompanyID: companyID,
})
if err != nil {
h.logger.Error("Failed to get bets", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")

View File

@ -37,7 +37,7 @@ type CreateCashierReq struct {
func (h *Handler) CreateCashier(c *fiber.Ctx) error {
// Get user_id from middleware
companyID := c.Locals("company_id").(domain.ValidInt64)
// companyID := c.Locals("company_id").(domain.ValidInt64)
var req CreateCashierReq
if err := c.BodyParser(&req); err != nil {
@ -48,6 +48,13 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
// Cashiers inherit the company id from the branch id
branch, err := h.branchSvc.GetBranchByID(c.Context(), req.BranchID)
if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is invalid", nil, nil)
}
userRequest := domain.CreateUserReq{
FirstName: req.FirstName,
LastName: req.LastName,
@ -56,7 +63,10 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
Password: req.Password,
Role: string(domain.RoleCashier),
Suspended: req.Suspended,
CompanyID: companyID,
CompanyID: domain.ValidInt64{
Value: branch.CompanyID,
Valid: true,
},
}
fmt.Print(req.Suspended)
newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true)

View File

@ -82,7 +82,7 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID)
if err != nil {
h.logger.Error("failed to fetch raw odds", "error", err)
// h.logger.Error("failed to fetch raw odds", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil)
}

View File

@ -225,35 +225,18 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
// @Router /transaction [get]
func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
// Get user_id from middleware
userID := c.Locals("user_id").(int64)
// Fetch user details
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to fetch user details", "user_id", userID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve user details", err, nil)
}
// userID := c.Locals("user_id").(int64)
// role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64)
var transactions []domain.Transaction
// Check user role and fetch transactions accordingly
// TODO: filtering by the user role
switch user.Role {
case domain.RoleSuperAdmin:
// Admin can fetch all transactions
transactions, err = h.transactionSvc.GetAllTransactions(c.Context())
case domain.RoleAdmin:
// Admins can fetch transaction for company branches
transactions, err = h.transactionSvc.GetAllTransactions(c.Context())
case domain.RoleBranchManager, domain.RoleCashier:
// Branch Manager or Cashier can fetch transactions for their branches
// transactions, err = transactionSvc.GetTransactionByBranch(c.Context(), user.BranchID)
transactions, err = h.transactionSvc.GetAllTransactions(c.Context())
default:
// Unauthorized role
return response.WriteJSON(c, fiber.StatusForbidden, "Unauthorized", nil, nil)
}
transactions, err := h.transactionSvc.GetAllTransactions(c.Context(), domain.TransactionFilter{
CompanyID: companyID,
BranchID: branchID,
})
if err != nil {
h.logger.Error("Failed to get transactions", "error", err)

View File

@ -53,6 +53,22 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
c.Locals("company_id", claim.CompanyID)
c.Locals("refresh_token", refreshToken)
var branchID domain.ValidInt64
if claim.Role == domain.RoleCashier {
branch, err := a.branchSvc.GetBranchByCashier(c.Context(), claim.UserId)
if err != nil {
a.logger.Error("Failed to get branch id for bet", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to branch id for bet")
}
branchID = domain.ValidInt64{
Value: branch.ID,
Valid: true,
}
}
c.Locals("branch_id", branchID)
return c.Next()
}

View File

@ -55,9 +55,9 @@ db-up:
.PHONY: db-down
db-down:
@docker compose down
@docker volume rm fortunebet-backend_postgres_data
postgres:
@docker exec -it fortunebet-backend-postgres-1 psql -U root -d gh
.PHONY: sqlc-gen
sqlc-gen:
@sqlc generate