feat: result checker fixed for market flags
This commit is contained in:
parent
90aee0c470
commit
acf54d4de7
63
README.md
63
README.md
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
# Directory Structure
|
||||
|
||||
├── .vscode
|
||||
│ ├── settings.json
|
||||
├── cmd
|
||||
│ ├── main.go
|
||||
├── db
|
||||
|
|
@ -12,16 +14,24 @@
|
|||
│ │ ├── 000002_notification.up.sql
|
||||
│ │ ├── 000003_referal.down.sql
|
||||
│ │ ├── 000003_referal.up.sql
|
||||
│ │ ├── 000004_virtual_game_Sessios.down.sql
|
||||
│ │ ├── 000004_virtual_game_Sessios.up.sql
|
||||
│ └── query
|
||||
│ ├── auth.sql
|
||||
│ ├── bet.sql
|
||||
│ ├── branch.sql
|
||||
│ ├── company.sql
|
||||
│ ├── events.sql
|
||||
│ ├── notification.sql
|
||||
│ ├── odds.sql
|
||||
│ ├── otp.sql
|
||||
│ ├── referal.sql
|
||||
│ ├── result.sql
|
||||
│ ├── ticket.sql
|
||||
│ ├── transactions.sql
|
||||
│ ├── transfer.sql
|
||||
│ ├── user.sql
|
||||
│ ├── virtual_games.sql
|
||||
│ ├── wallet.sql
|
||||
├── docs
|
||||
│ ├── docs.go
|
||||
|
|
@ -31,26 +41,37 @@
|
|||
│ └── db
|
||||
│ ├── auth.sql.go
|
||||
│ ├── bet.sql.go
|
||||
│ ├── branch.sql.go
|
||||
│ ├── company.sql.go
|
||||
│ ├── copyfrom.go
|
||||
│ ├── db.go
|
||||
│ ├── events.sql.go
|
||||
│ ├── models.go
|
||||
│ ├── notification.sql.go
|
||||
│ ├── odds.sql.go
|
||||
│ ├── otp.sql.go
|
||||
│ ├── referal.sql.go
|
||||
│ ├── result.sql.go
|
||||
│ ├── ticket.sql.go
|
||||
│ ├── transactions.sql.go
|
||||
│ ├── transfer.sql.go
|
||||
│ ├── user.sql.go
|
||||
│ ├── virtual_games.sql.go
|
||||
│ ├── wallet.sql.go
|
||||
└── internal
|
||||
├── config
|
||||
│ ├── config.go
|
||||
├── domain
|
||||
│ ├── auth.go
|
||||
│ ├── bank.go
|
||||
│ ├── bet.go
|
||||
│ ├── branch.go
|
||||
│ ├── chapa.go
|
||||
│ ├── common.go
|
||||
│ ├── company.go
|
||||
│ ├── event.go
|
||||
│ ├── notification.go
|
||||
│ ├── odds.go
|
||||
│ ├── otp.go
|
||||
│ ├── referal.go
|
||||
│ ├── role.go
|
||||
|
|
@ -58,6 +79,7 @@
|
|||
│ ├── transaction.go
|
||||
│ ├── transfer.go
|
||||
│ ├── user.go
|
||||
│ ├── virtual_game.go
|
||||
│ ├── wallet.go
|
||||
├── logger
|
||||
│ ├── logger.go
|
||||
|
|
@ -72,14 +94,20 @@
|
|||
├── repository
|
||||
│ ├── auth.go
|
||||
│ ├── bet.go
|
||||
│ ├── branch.go
|
||||
│ ├── company.go
|
||||
│ ├── event.go
|
||||
│ ├── notification.go
|
||||
│ ├── odds.go
|
||||
│ ├── otp.go
|
||||
│ ├── referal.go
|
||||
│ ├── result.go
|
||||
│ ├── store.go
|
||||
│ ├── ticket.go
|
||||
│ ├── transaction.go
|
||||
│ ├── transfer.go
|
||||
│ ├── user.go
|
||||
│ ├── virtual_game.go
|
||||
│ ├── wallet.go
|
||||
├── services
|
||||
│ ├── authentication
|
||||
|
|
@ -89,12 +117,27 @@
|
|||
│ ├── bet
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── branch
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── company
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── event
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── notfication
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── odds
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── referal
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── result
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── sportsbook
|
||||
│ │ ├── events.go
|
||||
│ │ ├── odds.go
|
||||
|
|
@ -105,30 +148,40 @@
|
|||
│ ├── transaction
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── transfer
|
||||
│ │ ├── chapa.go
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ ├── user
|
||||
│ │ ├── common.go
|
||||
│ │ ├── direct.go
|
||||
│ │ ├── port.go
|
||||
│ │ ├── register.go
|
||||
│ │ ├── reset.go
|
||||
│ │ ├── service.go
|
||||
│ │ ├── user.go
|
||||
│ ├── virtualGame
|
||||
│ │ ├── port.go
|
||||
│ │ ├── service.go
|
||||
│ └── wallet
|
||||
│ ├── chapa.go
|
||||
│ ├── port.go
|
||||
│ ├── service.go
|
||||
│ ├── transfer.go
|
||||
│ ├── wallet.go
|
||||
└── web_server
|
||||
├── handlers
|
||||
│ ├── auth_handler.go
|
||||
│ ├── bet_handler.go
|
||||
│ ├── branch_handler.go
|
||||
│ ├── cashier.go
|
||||
│ ├── company_handler.go
|
||||
│ ├── handlers.go
|
||||
│ ├── manager.go
|
||||
│ ├── notification_handler.go
|
||||
│ ├── prematch.go
|
||||
│ ├── referal_handlers.go
|
||||
│ ├── ticket_handler.go
|
||||
│ ├── transaction_handler.go
|
||||
│ ├── transfer_handler.go
|
||||
│ ├── user.go
|
||||
│ ├── virtual_games_hadlers.go
|
||||
│ ├── wallet_handler.go
|
||||
├── jwt
|
||||
│ ├── jwt.go
|
||||
|
|
@ -138,10 +191,10 @@
|
|||
└── validator
|
||||
├── validatord.go
|
||||
├── app.go
|
||||
├── cron.go
|
||||
├── middleware.go
|
||||
├── routes.go
|
||||
├── .air.toml
|
||||
├── .env
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
├── compose.db.yaml
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func main() {
|
|||
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(cfg.Bet365Token, store)
|
||||
resultSvc := result.NewService(cfg.Bet365Token,store)
|
||||
resultSvc := result.NewService(store, cfg, logger)
|
||||
ticketSvc := ticket.NewService(store)
|
||||
betSvc := bet.NewService(store)
|
||||
walletSvc := wallet.NewService(store, store)
|
||||
|
|
@ -92,7 +92,7 @@ func main() {
|
|||
JwtAccessKey: cfg.JwtKey,
|
||||
JwtAccessExpiry: cfg.AccessExpiry,
|
||||
}, userSvc,
|
||||
ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc)
|
||||
ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, resultSvc)
|
||||
logger.Info("Starting server", "port", cfg.Port)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
|
|
|
|||
|
|
@ -386,13 +386,3 @@ VALUES (
|
|||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
--------------------------------------------------Bet365 Data Fetching + Event Managment------------------------------------------------
|
||||
CREATE TABLE results (
|
||||
id SERIAL PRIMARY KEY,
|
||||
event_id TEXT UNIQUE,
|
||||
full_time_score TEXT,
|
||||
half_time_score TEXT,
|
||||
ss TEXT,
|
||||
scores JSONB,
|
||||
fetched_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
1
db/migrations/000005_result_checker.down.sql
Normal file
1
db/migrations/000005_result_checker.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS results;
|
||||
15
db/migrations/000005_result_checker.up.sql
Normal file
15
db/migrations/000005_result_checker.up.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS results (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
bet_outcome_id BIGINT NOT NULL,
|
||||
event_id BIGINT NOT NULL,
|
||||
odd_id BIGINT NOT NULL,
|
||||
market_id BIGINT NOT NULL,
|
||||
status INT NOT NULL,
|
||||
score VARCHAR(255),
|
||||
full_time_score VARCHAR(255),
|
||||
half_time_score VARCHAR(255),
|
||||
ss VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (bet_outcome_id) REFERENCES bet_outcomes (id)
|
||||
);
|
||||
|
|
@ -1,8 +1,53 @@
|
|||
-- name: InsertResult :one
|
||||
INSERT INTO results (event_id, full_time_score, half_time_score, ss, scores)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, event_id, full_time_score, half_time_score, ss, scores, fetched_at;
|
||||
-- name: CreateResult :one
|
||||
INSERT INTO results (
|
||||
bet_outcome_id,
|
||||
event_id,
|
||||
odd_id,
|
||||
market_id,
|
||||
status,
|
||||
score,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) RETURNING *;
|
||||
|
||||
-- name: InsertResult :exec
|
||||
INSERT INTO results (
|
||||
bet_outcome_id,
|
||||
event_id,
|
||||
odd_id,
|
||||
market_id,
|
||||
status,
|
||||
score,
|
||||
full_time_score,
|
||||
half_time_score,
|
||||
ss,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- name: GetResultByEventID :one
|
||||
SELECT * FROM results WHERE event_id = $1;
|
||||
-- name: GetResultByBetOutcomeID :one
|
||||
SELECT * FROM results WHERE bet_outcome_id = $1;
|
||||
|
||||
-- name: GetPendingBetOutcomes :many
|
||||
SELECT * FROM bet_outcomes WHERE status = 0 AND expires <= CURRENT_TIMESTAMP;
|
||||
|
|
|
|||
|
|
@ -270,13 +270,18 @@ type RefreshToken struct {
|
|||
}
|
||||
|
||||
type Result struct {
|
||||
ID int32 `json:"id"`
|
||||
EventID pgtype.Text `json:"event_id"`
|
||||
FullTimeScore pgtype.Text `json:"full_time_score"`
|
||||
HalfTimeScore pgtype.Text `json:"half_time_score"`
|
||||
Ss pgtype.Text `json:"ss"`
|
||||
Scores []byte `json:"scores"`
|
||||
FetchedAt pgtype.Timestamptz `json:"fetched_at"`
|
||||
ID int64 `json:"id"`
|
||||
BetOutcomeID int64 `json:"bet_outcome_id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
OddID int64 `json:"odd_id"`
|
||||
MarketID int64 `json:"market_id"`
|
||||
Status int32 `json:"status"`
|
||||
Score pgtype.Text `json:"score"`
|
||||
FullTimeScore pgtype.Text `json:"full_time_score"`
|
||||
HalfTimeScore pgtype.Text `json:"half_time_score"`
|
||||
Ss pgtype.Text `json:"ss"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SupportedOperation struct {
|
||||
|
|
|
|||
|
|
@ -11,56 +11,178 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const GetResultByEventID = `-- name: GetResultByEventID :one
|
||||
SELECT id, event_id, full_time_score, half_time_score, ss, scores, fetched_at FROM results WHERE event_id = $1
|
||||
const CreateResult = `-- name: CreateResult :one
|
||||
INSERT INTO results (
|
||||
bet_outcome_id,
|
||||
event_id,
|
||||
odd_id,
|
||||
market_id,
|
||||
status,
|
||||
score,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) RETURNING id, bet_outcome_id, event_id, odd_id, market_id, status, score, full_time_score, half_time_score, ss, created_at, updated_at
|
||||
`
|
||||
|
||||
func (q *Queries) GetResultByEventID(ctx context.Context, eventID pgtype.Text) (Result, error) {
|
||||
row := q.db.QueryRow(ctx, GetResultByEventID, eventID)
|
||||
type CreateResultParams struct {
|
||||
BetOutcomeID int64 `json:"bet_outcome_id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
OddID int64 `json:"odd_id"`
|
||||
MarketID int64 `json:"market_id"`
|
||||
Status int32 `json:"status"`
|
||||
Score pgtype.Text `json:"score"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateResult(ctx context.Context, arg CreateResultParams) (Result, error) {
|
||||
row := q.db.QueryRow(ctx, CreateResult,
|
||||
arg.BetOutcomeID,
|
||||
arg.EventID,
|
||||
arg.OddID,
|
||||
arg.MarketID,
|
||||
arg.Status,
|
||||
arg.Score,
|
||||
)
|
||||
var i Result
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.BetOutcomeID,
|
||||
&i.EventID,
|
||||
&i.OddID,
|
||||
&i.MarketID,
|
||||
&i.Status,
|
||||
&i.Score,
|
||||
&i.FullTimeScore,
|
||||
&i.HalfTimeScore,
|
||||
&i.Ss,
|
||||
&i.Scores,
|
||||
&i.FetchedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const InsertResult = `-- name: InsertResult :one
|
||||
INSERT INTO results (event_id, full_time_score, half_time_score, ss, scores)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, event_id, full_time_score, half_time_score, ss, scores, fetched_at
|
||||
const GetPendingBetOutcomes = `-- name: GetPendingBetOutcomes :many
|
||||
SELECT id, bet_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires FROM bet_outcomes WHERE status = 0 AND expires <= CURRENT_TIMESTAMP
|
||||
`
|
||||
|
||||
func (q *Queries) GetPendingBetOutcomes(ctx context.Context) ([]BetOutcome, error) {
|
||||
rows, err := q.db.Query(ctx, GetPendingBetOutcomes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BetOutcome
|
||||
for rows.Next() {
|
||||
var i BetOutcome
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.BetID,
|
||||
&i.EventID,
|
||||
&i.OddID,
|
||||
&i.HomeTeamName,
|
||||
&i.AwayTeamName,
|
||||
&i.MarketID,
|
||||
&i.MarketName,
|
||||
&i.Odd,
|
||||
&i.OddName,
|
||||
&i.OddHeader,
|
||||
&i.OddHandicap,
|
||||
&i.Status,
|
||||
&i.Expires,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetResultByBetOutcomeID = `-- name: GetResultByBetOutcomeID :one
|
||||
SELECT id, bet_outcome_id, event_id, odd_id, market_id, status, score, full_time_score, half_time_score, ss, created_at, updated_at FROM results WHERE bet_outcome_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetResultByBetOutcomeID(ctx context.Context, betOutcomeID int64) (Result, error) {
|
||||
row := q.db.QueryRow(ctx, GetResultByBetOutcomeID, betOutcomeID)
|
||||
var i Result
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.BetOutcomeID,
|
||||
&i.EventID,
|
||||
&i.OddID,
|
||||
&i.MarketID,
|
||||
&i.Status,
|
||||
&i.Score,
|
||||
&i.FullTimeScore,
|
||||
&i.HalfTimeScore,
|
||||
&i.Ss,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const InsertResult = `-- name: InsertResult :exec
|
||||
INSERT INTO results (
|
||||
bet_outcome_id,
|
||||
event_id,
|
||||
odd_id,
|
||||
market_id,
|
||||
status,
|
||||
score,
|
||||
full_time_score,
|
||||
half_time_score,
|
||||
ss,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
type InsertResultParams struct {
|
||||
EventID pgtype.Text `json:"event_id"`
|
||||
BetOutcomeID int64 `json:"bet_outcome_id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
OddID int64 `json:"odd_id"`
|
||||
MarketID int64 `json:"market_id"`
|
||||
Status int32 `json:"status"`
|
||||
Score pgtype.Text `json:"score"`
|
||||
FullTimeScore pgtype.Text `json:"full_time_score"`
|
||||
HalfTimeScore pgtype.Text `json:"half_time_score"`
|
||||
Ss pgtype.Text `json:"ss"`
|
||||
Scores []byte `json:"scores"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertResult(ctx context.Context, arg InsertResultParams) (Result, error) {
|
||||
row := q.db.QueryRow(ctx, InsertResult,
|
||||
func (q *Queries) InsertResult(ctx context.Context, arg InsertResultParams) error {
|
||||
_, err := q.db.Exec(ctx, InsertResult,
|
||||
arg.BetOutcomeID,
|
||||
arg.EventID,
|
||||
arg.OddID,
|
||||
arg.MarketID,
|
||||
arg.Status,
|
||||
arg.Score,
|
||||
arg.FullTimeScore,
|
||||
arg.HalfTimeScore,
|
||||
arg.Ss,
|
||||
arg.Scores,
|
||||
)
|
||||
var i Result
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.EventID,
|
||||
&i.FullTimeScore,
|
||||
&i.HalfTimeScore,
|
||||
&i.Ss,
|
||||
&i.Scores,
|
||||
&i.FetchedAt,
|
||||
)
|
||||
return i, err
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,16 +36,3 @@ func (m Currency) String() string {
|
|||
x = x / 100
|
||||
return fmt.Sprintf("$%.2f", x)
|
||||
}
|
||||
|
||||
type OutcomeStatus int
|
||||
|
||||
const (
|
||||
OUTCOME_STATUS_PENDING OutcomeStatus = iota
|
||||
OUTCOME_STATUS_WIN
|
||||
OUTCOME_STATUS_LOSS
|
||||
OUTCOME_STATUS_ERROR
|
||||
)
|
||||
|
||||
func (b OutcomeStatus) String() string {
|
||||
return []string{"Pending", "Win", "Loss", "Error"}[b]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,21 +46,11 @@ type MatchResult struct {
|
|||
Status string
|
||||
Scores map[string]map[string]string
|
||||
}
|
||||
type Result struct {
|
||||
EventID string
|
||||
FullTimeScore string
|
||||
HalfTimeScore string
|
||||
SS string
|
||||
Scores map[string]Score // "1": {"home": "0", "away": "1"}
|
||||
}
|
||||
type Score struct {
|
||||
Home string `json:"home"`
|
||||
Away string `json:"away"`
|
||||
}
|
||||
|
||||
type Odds struct {
|
||||
ID int64 `json:"id"`
|
||||
EventID string `json:"event_id"`
|
||||
MarketType string `json:"market_type"`
|
||||
Name string `json:"name"`
|
||||
HitStatus string `json:"hit_status"`
|
||||
}
|
||||
ID int64 `json:"id"`
|
||||
EventID string `json:"event_id"`
|
||||
MarketType string `json:"market_type"`
|
||||
Name string `json:"name"`
|
||||
HitStatus string `json:"hit_status"`
|
||||
}
|
||||
|
|
|
|||
105
internal/domain/result.go
Normal file
105
internal/domain/result.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ResultResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []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"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Attacks []string `json:"attacks"`
|
||||
Corners []string `json:"corners"`
|
||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||
Goals []string `json:"goals"`
|
||||
OffTarget []string `json:"off_target"`
|
||||
OnTarget []string `json:"on_target"`
|
||||
Penalties []string `json:"penalties"`
|
||||
PossessionRT []string `json:"possession_rt"`
|
||||
RedCards []string `json:"redcards"`
|
||||
Substitutions []string `json:"substitutions"`
|
||||
YellowCards []string `json:"yellowcards"`
|
||||
} `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"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
type Score struct {
|
||||
Home string `json:"home"`
|
||||
Away string `json:"away"`
|
||||
}
|
||||
|
||||
type MarketConfig struct {
|
||||
Sport string
|
||||
MarketCategories map[string]bool
|
||||
MarketTypes map[string]bool
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
ID int64
|
||||
BetOutcomeID int64
|
||||
EventID int64
|
||||
OddID int64
|
||||
MarketID int64
|
||||
Status OutcomeStatus
|
||||
Score string
|
||||
FullTimeScore string
|
||||
HalfTimeScore string
|
||||
SS string
|
||||
Scores map[string]Score
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateResult struct {
|
||||
BetOutcomeID int64
|
||||
EventID int64
|
||||
OddID int64
|
||||
MarketID int64
|
||||
Status OutcomeStatus
|
||||
Score string
|
||||
}
|
||||
|
||||
type OutcomeStatus int32
|
||||
|
||||
const (
|
||||
OUTCOME_STATUS_PENDING OutcomeStatus = 0
|
||||
OUTCOME_STATUS_WIN OutcomeStatus = 1
|
||||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||
OUTCOME_STATUS_VOID OutcomeStatus = 3
|
||||
)
|
||||
|
|
@ -2,60 +2,99 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func (s *Store) InsertResult(ctx context.Context, result domain.Result) error {
|
||||
scoresJSON, err := json.Marshal(result.Scores)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal Scores: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.queries.InsertResult(ctx, dbgen.InsertResultParams{
|
||||
EventID: pgtype.Text{String: result.EventID, Valid: true},
|
||||
FullTimeScore: pgtype.Text{String: result.FullTimeScore, Valid: true},
|
||||
HalfTimeScore: pgtype.Text{String: result.HalfTimeScore, Valid: true},
|
||||
Ss: pgtype.Text{String: result.SS, Valid: true},
|
||||
Scores: scoresJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert result: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
func convertDBResult(result dbgen.Result) domain.Result {
|
||||
scores := make(map[string]domain.Score)
|
||||
return domain.Result{
|
||||
ID: result.ID,
|
||||
BetOutcomeID: result.BetOutcomeID,
|
||||
EventID: result.EventID,
|
||||
OddID: result.OddID,
|
||||
MarketID: result.MarketID,
|
||||
Status: domain.OutcomeStatus(result.Status),
|
||||
Score: result.Score.String,
|
||||
FullTimeScore: result.FullTimeScore.String,
|
||||
HalfTimeScore: result.HalfTimeScore.String,
|
||||
SS: result.Ss.String,
|
||||
Scores: scores,
|
||||
CreatedAt: result.CreatedAt.Time,
|
||||
UpdatedAt: result.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) GetResultByEventID(ctx context.Context, eventID string) (domain.Result, error) {
|
||||
eventIDText := pgtype.Text{String: eventID, Valid: true}
|
||||
func convertCreateResult(result domain.CreateResult) dbgen.CreateResultParams {
|
||||
return dbgen.CreateResultParams{
|
||||
BetOutcomeID: result.BetOutcomeID,
|
||||
EventID: result.EventID,
|
||||
OddID: result.OddID,
|
||||
MarketID: result.MarketID,
|
||||
Status: int32(result.Status),
|
||||
Score: pgtype.Text{String: result.Score},
|
||||
}
|
||||
}
|
||||
|
||||
result, err := s.queries.GetResultByEventID(ctx, eventIDText)
|
||||
if err != nil {
|
||||
return domain.Result{}, fmt.Errorf("failed to get result by event ID: %w", err)
|
||||
}
|
||||
func convertResult(result domain.Result) dbgen.InsertResultParams {
|
||||
return dbgen.InsertResultParams{
|
||||
BetOutcomeID: result.BetOutcomeID,
|
||||
EventID: result.EventID,
|
||||
OddID: result.OddID,
|
||||
MarketID: result.MarketID,
|
||||
Status: int32(result.Status),
|
||||
Score: pgtype.Text{String: result.Score},
|
||||
FullTimeScore: pgtype.Text{String: result.FullTimeScore},
|
||||
HalfTimeScore: pgtype.Text{String: result.HalfTimeScore},
|
||||
Ss: pgtype.Text{String: result.SS},
|
||||
}
|
||||
}
|
||||
|
||||
var rawScores map[string]map[string]string
|
||||
if err := json.Unmarshal(result.Scores, &rawScores); err != nil {
|
||||
return domain.Result{}, fmt.Errorf("failed to unmarshal scores: %w", err)
|
||||
}
|
||||
func (s *Store) CreateResult(ctx context.Context, result domain.CreateResult) (domain.Result, error) {
|
||||
dbResult, err := s.queries.CreateResult(ctx, convertCreateResult(result))
|
||||
if err != nil {
|
||||
return domain.Result{}, err
|
||||
}
|
||||
return convertDBResult(dbResult), nil
|
||||
}
|
||||
|
||||
scores := make(map[string]domain.Score)
|
||||
for key, value := range rawScores {
|
||||
scores[key] = domain.Score{
|
||||
Home: value["home"],
|
||||
Away: value["away"],
|
||||
}
|
||||
}
|
||||
func (s *Store) InsertResult(ctx context.Context, result domain.Result) error {
|
||||
return s.queries.InsertResult(ctx, convertResult(result))
|
||||
}
|
||||
|
||||
return domain.Result{
|
||||
EventID: result.EventID.String,
|
||||
FullTimeScore: result.FullTimeScore.String,
|
||||
HalfTimeScore: result.HalfTimeScore.String,
|
||||
SS: result.Ss.String,
|
||||
Scores: scores,
|
||||
}, nil
|
||||
}
|
||||
func (s *Store) GetResultByBetOutcomeID(ctx context.Context, betOutcomeID int64) (domain.Result, error) {
|
||||
dbResult, err := s.queries.GetResultByBetOutcomeID(ctx, betOutcomeID)
|
||||
if err != nil {
|
||||
return domain.Result{}, err
|
||||
}
|
||||
return convertDBResult(dbResult), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPendingBetOutcomes(ctx context.Context) ([]domain.BetOutcome, error) {
|
||||
dbOutcomes, err := s.queries.GetPendingBetOutcomes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outcomes := make([]domain.BetOutcome, 0, len(dbOutcomes))
|
||||
for _, dbOutcome := range dbOutcomes {
|
||||
outcomes = append(outcomes, domain.BetOutcome{
|
||||
ID: dbOutcome.ID,
|
||||
BetID: dbOutcome.BetID,
|
||||
EventID: dbOutcome.EventID,
|
||||
OddID: dbOutcome.OddID,
|
||||
HomeTeamName: dbOutcome.HomeTeamName,
|
||||
AwayTeamName: dbOutcome.AwayTeamName,
|
||||
MarketID: dbOutcome.MarketID,
|
||||
MarketName: dbOutcome.MarketName,
|
||||
Odd: dbOutcome.Odd,
|
||||
OddName: dbOutcome.OddName,
|
||||
OddHeader: dbOutcome.OddHeader,
|
||||
OddHandicap: dbOutcome.OddHandicap,
|
||||
Status: domain.OutcomeStatus(dbOutcome.Status),
|
||||
Expires: dbOutcome.Expires.Time,
|
||||
})
|
||||
}
|
||||
return outcomes, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ package result
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
InsertResult(ctx context.Context, result domain.Result) error
|
||||
GetResultByEventID(ctx context.Context, eventID string) (domain.Result, error)
|
||||
type ResultService interface {
|
||||
FetchAndProcessResults(ctx context.Context) error
|
||||
FetchAndStoreResult(ctx context.Context, eventID string) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1,436 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
FetchAndStoreResult(ctx context.Context, eventID string) error
|
||||
type Service struct {
|
||||
repo *repository.Store
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type service struct {
|
||||
token string
|
||||
store *repository.Store
|
||||
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger) *Service {
|
||||
return &Service{
|
||||
repo: repo,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func NewService(token string, store *repository.Store) Service {
|
||||
return &service{
|
||||
token: token,
|
||||
store: store,
|
||||
}
|
||||
var supportedMarkets = map[string]domain.MarketConfig{
|
||||
"football": {
|
||||
Sport: "football",
|
||||
MarketCategories: map[string]bool{
|
||||
"main": true,
|
||||
"asian_lines": true,
|
||||
"goals": true,
|
||||
"half": true,
|
||||
},
|
||||
MarketTypes: map[string]bool{
|
||||
"full_time_result": true,
|
||||
"double_chance": true,
|
||||
"goals_over_under": true,
|
||||
"correct_score": true,
|
||||
"asian_handicap": true,
|
||||
"goal_line": true,
|
||||
"half_time_result": true,
|
||||
"1st_half_asian_handicap": true,
|
||||
"1st_half_goal_line": true,
|
||||
"first_team_to_score": true,
|
||||
"goals_odd_even": true,
|
||||
"draw_no_bet": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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.token, eventID)
|
||||
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||
outcomes, err := s.repo.GetPendingBetOutcomes(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch result: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
for _, outcome := range outcomes {
|
||||
if outcome.Expires.After(time.Now()) {
|
||||
continue
|
||||
}
|
||||
|
||||
var apiResp struct {
|
||||
Results []struct {
|
||||
SS string
|
||||
Scores map[string]domain.Score `json:"scores"`
|
||||
} `json:"results"`
|
||||
}
|
||||
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
|
||||
return fmt.Errorf("failed to decode result: %w", err)
|
||||
}
|
||||
if len(apiResp.Results) == 0 {
|
||||
return fmt.Errorf("no result returned from API")
|
||||
}
|
||||
_, err = s.repo.CreateResult(ctx, domain.CreateResult{
|
||||
BetOutcomeID: outcome.ID,
|
||||
EventID: outcome.EventID,
|
||||
OddID: outcome.OddID,
|
||||
MarketID: outcome.MarketID,
|
||||
Status: result.Status,
|
||||
Score: result.Score,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to store result", "bet_outcome_id", outcome.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
r := apiResp.Results[0]
|
||||
err = s.repo.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
halfScore := ""
|
||||
if s1, ok := r.Scores["1"]; ok {
|
||||
halfScore = fmt.Sprintf("%s-%s", s1.Home, s1.Away)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
result := domain.Result{
|
||||
EventID: eventID,
|
||||
FullTimeScore: r.SS,
|
||||
HalfTimeScore: halfScore,
|
||||
SS: r.SS,
|
||||
Scores: r.Scores,
|
||||
}
|
||||
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)
|
||||
|
||||
return s.store.InsertResult(ctx, result)
|
||||
}
|
||||
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.ResultResponse
|
||||
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 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)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create request", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", resp.StatusCode)
|
||||
return domain.CreateResult{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var resultResp domain.ResultResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
|
||||
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
if resultResp.Success != 1 || len(resultResp.Results) == 0 {
|
||||
s.logger.Error("Invalid API response", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("invalid API response")
|
||||
}
|
||||
|
||||
result := resultResp.Results[0]
|
||||
if result.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
|
||||
finalScore := parseScore(result.SS)
|
||||
firstHalfScore := parseScore(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
||||
|
||||
corners := parseStats(result.Stats.Corners)
|
||||
status, err := s.evaluateOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
return domain.CreateResult{
|
||||
BetOutcomeID: 0,
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
Status: status,
|
||||
Score: result.SS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseScore(scoreStr string) struct{ Home, Away int } {
|
||||
parts := strings.Split(scoreStr, "-")
|
||||
if len(parts) != 2 {
|
||||
return struct{ Home, Away int }{0, 0}
|
||||
}
|
||||
home, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
away, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
return struct{ Home, Away int }{Home: home, Away: away}
|
||||
}
|
||||
|
||||
func parseStats(stats []string) struct{ Home, Away int } {
|
||||
if len(stats) != 2 {
|
||||
return struct{ Home, Away int }{0, 0}
|
||||
}
|
||||
home, _ := strconv.Atoi(stats[0])
|
||||
away, _ := strconv.Atoi(stats[1])
|
||||
return struct{ Home, Away int }{Home: home, Away: away}
|
||||
}
|
||||
|
||||
// evaluateOutcome determines the outcome status based on market type and odd
|
||||
func (s *Service) evaluateOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore struct{ Home, Away int }, corners struct{ Home, Away int }, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||
marketConfig := supportedMarkets["football"]
|
||||
if !marketConfig.MarketTypes[outcome.MarketName] {
|
||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "full_time_result":
|
||||
return evaluateFullTimeResult(outcome, finalScore)
|
||||
case "goals_over_under":
|
||||
return evaluateGoalsOverUnder(outcome, finalScore)
|
||||
case "correct_score":
|
||||
return evaluateCorrectScore(outcome, finalScore)
|
||||
case "half_time_result":
|
||||
return evaluateHalfTimeResult(outcome, firstHalfScore)
|
||||
case "asian_handicap":
|
||||
return evaluateAsianHandicap(outcome, finalScore)
|
||||
case "goal_line":
|
||||
return evaluateGoalLine(outcome, finalScore)
|
||||
case "1st_half_asian_handicap":
|
||||
return evaluateAsianHandicap(outcome, firstHalfScore)
|
||||
case "1st_half_goal_line":
|
||||
return evaluateGoalLine(outcome, firstHalfScore)
|
||||
case "first_team_to_score":
|
||||
return evaluateFirstTeamToScore(outcome, events)
|
||||
case "goals_odd_even":
|
||||
return evaluateGoalsOddEven(outcome, finalScore)
|
||||
case "double_chance":
|
||||
return evaluateDoubleChance(outcome, finalScore)
|
||||
case "draw_no_bet":
|
||||
return evaluateDrawNoBet(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 evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddName {
|
||||
case "1": // Home win
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "Draw":
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2": // Away win
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalGoals := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalGoals > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalGoals == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalGoals < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalGoals == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
|
||||
if outcome.OddName == expectedScore {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
return evaluateFullTimeResult(outcome, score)
|
||||
}
|
||||
|
||||
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" { // Home team
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" { // Away team
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
return evaluateGoalsOverUnder(outcome, score)
|
||||
}
|
||||
|
||||
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||
for _, event := range events {
|
||||
if strings.Contains(event["text"], "1st Goal") {
|
||||
if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
||||
}
|
||||
|
||||
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalGoals := score.Home + score.Away
|
||||
isOdd := totalGoals%2 == 1
|
||||
if outcome.OddName == "Odd" && isOdd {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if outcome.OddName == "Even" && !isOdd {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
isHomeWin := score.Home > score.Away
|
||||
isDraw := score.Home == score.Away
|
||||
isAwayWin := score.Away > score.Home
|
||||
|
||||
switch outcome.OddName {
|
||||
case "1 or Draw":
|
||||
if isHomeWin || isDraw {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "Draw or 2":
|
||||
if isDraw || isAwayWin {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "1 or 2":
|
||||
if isHomeWin || isAwayWin {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
if outcome.OddName == "1" && score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if outcome.OddName == "2" && score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import (
|
|||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
|
|
@ -45,6 +46,7 @@ type App struct {
|
|||
Logger *slog.Logger
|
||||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
resultSvc *result.Service
|
||||
}
|
||||
|
||||
func NewApp(
|
||||
|
|
@ -64,6 +66,7 @@ func NewApp(
|
|||
eventSvc event.Service,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||
resultSvc *result.Service,
|
||||
) *App {
|
||||
app := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
|
|
@ -99,6 +102,7 @@ func NewApp(
|
|||
prematchSvc: prematchSvc,
|
||||
eventSvc: eventSvc,
|
||||
virtualGameSvc: virtualGameSvc,
|
||||
resultSvc: resultSvc,
|
||||
}
|
||||
|
||||
s.initAppRoutes()
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"context"
|
||||
"log"
|
||||
|
||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/robfig/cron/v3"
|
||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service, resultService resultsvc.Service) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service, resultService *resultsvc.Service) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
// {
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
// {
|
||||
// spec: "*/5 * * * * *", // Every 5 seconds
|
||||
// task: func() {
|
||||
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
|
|
@ -25,34 +25,34 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
// }
|
||||
// },
|
||||
// },
|
||||
|
||||
// {
|
||||
// spec: "*/5 * * * * *", // Every 5 seconds
|
||||
// task: func() {
|
||||
// if err := eventService.FetchLiveEvents(context.Background()); err != nil {
|
||||
// log.Printf("FetchLiveEvents error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// spec: "0 */15 * * * *", // Every 15 minutes
|
||||
// task: func() {
|
||||
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||
// log.Printf("FetchNonLiveOdds error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
|
||||
// {
|
||||
// spec: "*/5 * * * * *", // Every 5 seconds
|
||||
// task: func() {
|
||||
// if err := eventService.FetchLiveEvents(context.Background()); err != nil {
|
||||
// log.Printf("FetchLiveEvents error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// spec: "0 */15 * * * *", // Every 15 minutes
|
||||
// task: func() {
|
||||
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||
// log.Printf("FetchNonLiveOdds error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
spec: "*/10 * * * * *",
|
||||
task: func() {
|
||||
log.Println("Fetching results for upcoming events...")
|
||||
|
||||
|
||||
upcomingEvents, err := eventService.GetAllUpcomingEvents(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch upcoming events: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
for _, event := range upcomingEvents {
|
||||
if err := resultService.FetchAndStoreResult(context.Background(), event.ID); err != nil {
|
||||
log.Printf(" Failed to fetch/store result for event %s: %v", event.ID, err)
|
||||
|
|
@ -64,12 +64,12 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
},
|
||||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
log.Fatalf("Failed to schedule cron job: %v", err)
|
||||
}
|
||||
}
|
||||
for _, job := range schedule {
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
log.Fatalf("Failed to schedule cron job: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
log.Println("Cron jobs started for event and odds services")
|
||||
}
|
||||
c.Start()
|
||||
log.Println("Cron jobs started for event and odds services")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user