resolve conflict

This commit is contained in:
Asher Samuel 2025-06-06 03:43:41 +03:00
commit d3506fd03a
22 changed files with 673 additions and 278 deletions

View File

@ -75,4 +75,5 @@ DROP TABLE IF EXISTS supported_operations;
DROP TABLE IF EXISTS refresh_tokens;
DROP TABLE IF EXISTS otps;
DROP TABLE IF EXISTS odds;
DROP TABLE IF EXISTS events;
DROP TABLE IF EXISTS events;
DROP TABLE IF EXISTS leagues;

View File

@ -184,15 +184,15 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
);
CREATE TABLE events (
id TEXT PRIMARY KEY,
sport_id TEXT,
sport_id INT,
match_name TEXT,
home_team TEXT,
away_team TEXT,
home_team_id TEXT,
away_team_id TEXT,
home_team_id INT,
away_team_id INT,
home_kit_image TEXT,
away_kit_image TEXT,
league_id TEXT,
league_id INT,
league_name TEXT,
league_cc TEXT,
start_time TIMESTAMP,
@ -233,6 +233,13 @@ CREATE TABLE companies (
admin_id BIGINT NOT NULL,
wallet_id BIGINT NOT NULL
);
CREATE TABLE leagues (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL,
country_code TEXT,
bet365_id INT,
is_active BOOLEAN DEFAULT true
);
-- Views
CREATE VIEW companies_details AS
SELECT companies.*,

37
db/query/leagues.sql Normal file
View File

@ -0,0 +1,37 @@
-- name: InsertLeague :exec
INSERT INTO leagues (
id,
name,
country_code,
bet365_id,
is_active
) VALUES (
$1, $2, $3, $4, $5
)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
country_code = EXCLUDED.country_code,
bet365_id = EXCLUDED.bet365_id,
is_active = EXCLUDED.is_active;
-- name: GetSupportedLeagues :many
SELECT id,
name,
country_code,
bet365_id,
is_active
FROM leagues
WHERE is_active = true;
-- name: CheckLeagueSupport :one
SELECT EXISTS(
SELECT 1
FROM leagues
WHERE id = $1
AND is_active = true
);
-- name: UpdateLeague :exec
UPDATE leagues
SET name = $1,
country_code = $2,
bet365_id = $3,
is_active = $4
WHERE id = $5;

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: cashier.sql
package dbgen

View File

@ -47,15 +47,15 @@ ORDER BY start_time ASC
type GetAllUpcomingEventsRow struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -128,15 +128,15 @@ ORDER BY start_time ASC
type GetExpiredUpcomingEventsRow struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -226,8 +226,8 @@ LIMIT $6 OFFSET $5
`
type GetPaginatedUpcomingEventsParams struct {
LeagueID pgtype.Text `json:"league_id"`
SportID pgtype.Text `json:"sport_id"`
LeagueID pgtype.Int4 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
Offset pgtype.Int4 `json:"offset"`
@ -236,15 +236,15 @@ type GetPaginatedUpcomingEventsParams struct {
type GetPaginatedUpcomingEventsRow struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -323,8 +323,8 @@ WHERE is_live = false
`
type GetTotalEventsParams struct {
LeagueID pgtype.Text `json:"league_id"`
SportID pgtype.Text `json:"sport_id"`
LeagueID pgtype.Int4 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
}
@ -368,15 +368,15 @@ LIMIT 1
type GetUpcomingByIDRow struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -484,15 +484,15 @@ SET sport_id = EXCLUDED.sport_id,
type InsertEventParams struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -591,15 +591,15 @@ SET sport_id = EXCLUDED.sport_id,
type InsertUpcomingEventParams struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`

128
gen/db/leagues.sql.go Normal file
View File

@ -0,0 +1,128 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: leagues.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CheckLeagueSupport = `-- name: CheckLeagueSupport :one
SELECT EXISTS(
SELECT 1
FROM leagues
WHERE id = $1
AND is_active = true
)
`
func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error) {
row := q.db.QueryRow(ctx, CheckLeagueSupport, id)
var exists bool
err := row.Scan(&exists)
return exists, err
}
const GetSupportedLeagues = `-- name: GetSupportedLeagues :many
SELECT id,
name,
country_code,
bet365_id,
is_active
FROM leagues
WHERE is_active = true
`
func (q *Queries) GetSupportedLeagues(ctx context.Context) ([]League, error) {
rows, err := q.db.Query(ctx, GetSupportedLeagues)
if err != nil {
return nil, err
}
defer rows.Close()
var items []League
for rows.Next() {
var i League
if err := rows.Scan(
&i.ID,
&i.Name,
&i.CountryCode,
&i.Bet365ID,
&i.IsActive,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const InsertLeague = `-- name: InsertLeague :exec
INSERT INTO leagues (
id,
name,
country_code,
bet365_id,
is_active
) VALUES (
$1, $2, $3, $4, $5
)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
country_code = EXCLUDED.country_code,
bet365_id = EXCLUDED.bet365_id,
is_active = EXCLUDED.is_active
`
type InsertLeagueParams struct {
ID int64 `json:"id"`
Name string `json:"name"`
CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"`
IsActive pgtype.Bool `json:"is_active"`
}
func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) error {
_, err := q.db.Exec(ctx, InsertLeague,
arg.ID,
arg.Name,
arg.CountryCode,
arg.Bet365ID,
arg.IsActive,
)
return err
}
const UpdateLeague = `-- name: UpdateLeague :exec
UPDATE leagues
SET name = $1,
country_code = $2,
bet365_id = $3,
is_active = $4
WHERE id = $5
`
type UpdateLeagueParams struct {
Name string `json:"name"`
CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"`
IsActive pgtype.Bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) error {
_, err := q.db.Exec(ctx, UpdateLeague,
arg.Name,
arg.CountryCode,
arg.Bet365ID,
arg.IsActive,
arg.ID,
)
return err
}

View File

@ -178,15 +178,15 @@ type CustomerWallet struct {
type Event struct {
ID string `json:"id"`
SportID pgtype.Text `json:"sport_id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Text `json:"home_team_id"`
AwayTeamID pgtype.Text `json:"away_team_id"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
@ -201,6 +201,14 @@ type Event struct {
Source pgtype.Text `json:"source"`
}
type League struct {
ID int64 `json:"id"`
Name string `json:"name"`
CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"`
IsActive pgtype.Bool `json:"is_active"`
}
type Notification struct {
ID string `json:"id"`
RecipientID int64 `json:"recipient_id"`

View File

@ -13,6 +13,10 @@ type ValidInt struct {
Value int
Valid bool
}
type ValidInt32 struct {
Value int32
Valid bool
}
type ValidString struct {
Value string

View File

@ -4,15 +4,15 @@ import "time"
type Event struct {
ID string
SportID string
SportID int32
MatchName string
HomeTeam string
AwayTeam string
HomeTeamID string
AwayTeamID string
HomeTeamID int32
AwayTeamID int32
HomeKitImage string
AwayKitImage string
LeagueID string
LeagueID int32
LeagueName string
LeagueCC string
StartTime string
@ -54,15 +54,15 @@ type BetResult struct {
type UpcomingEvent struct {
ID string // Event ID
SportID string // Sport ID
SportID int32 // Sport ID
MatchName string // Match or event name
HomeTeam string // Home team name (if available)
AwayTeam string // Away team name (can be empty/null)
HomeTeamID string // Home team ID
AwayTeamID string // Away team ID (can be empty/null)
HomeTeamID int32 // Home team ID
AwayTeamID int32 // Away team ID (can be empty/null)
HomeKitImage string // Kit or image for home team (optional)
AwayKitImage string // Kit or image for away team (optional)
LeagueID string // League ID
LeagueID int32 // League ID
LeagueName string // League name
LeagueCC string // League country code
StartTime time.Time // Converted from "time" field in UNIX format

View File

@ -1,66 +1,9 @@
package domain
// TODO Will make this dynamic by moving into the database
var SupportedLeagues = []int64{
// Football
10041282, //Premier League
10083364, //La Liga
10041095, //German Bundesliga
10041100, //Ligue 1
10041809, //UEFA Champions League
10041957, //UEFA Europa League
10079560, //UEFA Conference League
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
10041088, //Premier League 2
10084250, //Turkiye Super League
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
10084178, //Kenya Premier League
10043548, //International Women
// Ice Hockey
10037477, //NHL
10037447, //AHL
10074238, // AIHL
10069385, //IIHF World Championship
// Cricket
type League struct {
ID int64
Name string
CountryCode string
Bet365ID int32
IsActive bool
}

View File

@ -1,7 +1,6 @@
package domain
import (
"encoding/json"
"time"
)
@ -15,10 +14,11 @@ type Market struct {
MarketName string
MarketID string
UpdatedAt time.Time
Odds []json.RawMessage
Odds []map[string]interface{}
Name string
Handicap string
OddsVal float64
Source string
}
type Odd struct {

View File

@ -9,7 +9,7 @@ type BaseResultResponse struct {
Results []json.RawMessage `json:"results"`
}
type League struct {
type LeagueRes struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
@ -28,14 +28,14 @@ type Score struct {
}
type FootballResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League League `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League LeagueRes `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstHalf Score `json:"1"`
SecondHalf Score `json:"2"`
@ -67,14 +67,14 @@ type FootballResultResponse struct {
}
type BasketballResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League League `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League LeagueRes `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstQuarter Score `json:"1"`
SecondQuarter Score `json:"2"`
@ -114,14 +114,14 @@ type BasketballResultResponse struct {
Bet365ID string `json:"bet365_id"`
}
type IceHockeyResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League League `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League LeagueRes `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstPeriod Score `json:"1"`
SecondPeriod Score `json:"2"`

View File

@ -21,15 +21,15 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{
ID: e.ID,
SportID: pgtype.Text{String: e.SportID, Valid: true},
SportID: pgtype.Int4{Int32: e.SportID, Valid: true},
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
LeagueID: pgtype.Text{String: e.LeagueID, Valid: true},
LeagueID: pgtype.Int4{Int32: e.LeagueID, Valid: true},
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: parsedTime, Valid: true},
@ -46,15 +46,15 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
ID: e.ID,
SportID: pgtype.Text{String: e.SportID, Valid: true},
SportID: pgtype.Int4{Int32: e.SportID, Valid: true},
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
LeagueID: pgtype.Text{String: e.LeagueID, Valid: true},
LeagueID: pgtype.Int4{Int32: e.LeagueID, Valid: true},
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true},
@ -75,15 +75,15 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID,
SportID: e.SportID.String,
SportID: e.SportID.Int32,
MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String,
AwayTeamID: e.AwayTeamID.String,
HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String,
LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
@ -103,15 +103,15 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID,
SportID: e.SportID.String,
SportID: e.SportID.Int32,
MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String,
AwayTeamID: e.AwayTeamID.String,
HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String,
LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
@ -121,16 +121,16 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
return upcomingEvents, nil
}
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
LeagueID: pgtype.Text{
String: leagueID.Value,
Valid: leagueID.Valid,
LeagueID: pgtype.Int4{
Int32: leagueID.Value,
Valid: leagueID.Valid,
},
SportID: pgtype.Text{
String: sportID.Value,
Valid: sportID.Valid,
SportID: pgtype.Int4{
Int32: sportID.Value,
Valid: sportID.Valid,
},
Limit: pgtype.Int4{
Int32: int32(limit.Value),
@ -157,15 +157,15 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.Val
for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID,
SportID: e.SportID.String,
SportID: e.SportID.Int32,
MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String,
AwayTeamID: e.AwayTeamID.String,
HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String,
LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
@ -173,13 +173,13 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.Val
}
}
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
LeagueID: pgtype.Text{
String: leagueID.Value,
Valid: leagueID.Valid,
LeagueID: pgtype.Int4{
Int32: leagueID.Value,
Valid: leagueID.Valid,
},
SportID: pgtype.Text{
String: sportID.Value,
Valid: sportID.Valid,
SportID: pgtype.Int4{
Int32: sportID.Value,
Valid: sportID.Valid,
},
FirstStartTime: pgtype.Timestamp{
Time: firstStartTime.Value.UTC(),
@ -205,15 +205,15 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
return domain.UpcomingEvent{
ID: event.ID,
SportID: event.SportID.String,
SportID: event.SportID.Int32,
MatchName: event.MatchName.String,
HomeTeam: event.HomeTeam.String,
AwayTeam: event.AwayTeam.String,
HomeTeamID: event.HomeTeamID.String,
AwayTeamID: event.AwayTeamID.String,
HomeTeamID: event.HomeTeamID.Int32,
AwayTeamID: event.AwayTeamID.Int32,
HomeKitImage: event.HomeKitImage.String,
AwayKitImage: event.AwayKitImage.String,
LeagueID: event.LeagueID.String,
LeagueID: event.LeagueID.Int32,
LeagueName: event.LeagueName.String,
LeagueCC: event.LeagueCc.String,
StartTime: event.StartTime.Time.UTC(),

View File

@ -0,0 +1,61 @@
package repository
import (
"context"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
)
func (s *Store) SaveLeague(ctx context.Context, l domain.League) error {
return s.queries.InsertLeague(ctx, dbgen.InsertLeagueParams{
ID: l.ID,
Name: l.Name,
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
})
}
func (s *Store) GetSupportedLeagues(ctx context.Context) ([]domain.League, error) {
leagues, err := s.queries.GetSupportedLeagues(ctx)
if err != nil {
return nil, err
}
supportedLeagues := make([]domain.League, len(leagues))
for i, league := range leagues {
supportedLeagues[i] = domain.League{
ID: league.ID,
Name: league.Name,
CountryCode: league.CountryCode.String,
Bet365ID: league.Bet365ID.Int32,
IsActive: league.IsActive.Bool,
}
}
return supportedLeagues, nil
}
func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, error) {
return s.queries.CheckLeagueSupport(ctx, leagueID)
}
// TODO: change to only take league id instad of the whole league
func (s *Store) SetLeagueActive(ctx context.Context, l domain.League) error {
return s.queries.UpdateLeague(ctx, dbgen.UpdateLeagueParams{
Name: l.Name,
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
IsActive: pgtype.Bool{Bool: true, Valid: true},
})
}
func (s *Store) SetLeagueInActive(ctx context.Context, l domain.League) error {
return s.queries.UpdateLeague(ctx, dbgen.UpdateLeagueParams{
Name: l.Name,
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
IsActive: pgtype.Bool{Bool: false, Valid: true},
})
}

View File

@ -17,15 +17,19 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
return nil
}
for _, raw := range m.Odds {
var item map[string]interface{}
if err := json.Unmarshal(raw, &item); err != nil {
continue
}
for _, item := range m.Odds {
var name string
var oddsVal float64
name := getString(item["name"])
if m.Source == "bwin" {
nameValue := getMap(item["name"])
name = getString(nameValue["value"])
oddsVal = getFloat(item["odds"])
} else {
name = getString(item["name"])
oddsVal = getConvertedFloat(item["odds"])
}
handicap := getString(item["handicap"])
oddsVal := getFloat(item["odds"])
rawOddsBytes, _ := json.Marshal(m.Odds)
@ -43,7 +47,7 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
Category: pgtype.Text{Valid: false},
RawOdds: rawOddsBytes,
IsActive: pgtype.Bool{Bool: true, Valid: true},
Source: pgtype.Text{String: "b365api", Valid: true},
Source: pgtype.Text{String: m.Source, Valid: true},
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
}
@ -85,23 +89,6 @@ func writeFailedMarketLog(m domain.Market, err error) error {
return writeErr
}
func getString(v interface{}) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
func getFloat(v interface{}) float64 {
if s, ok := v.(string); ok {
f, err := strconv.ParseFloat(s, 64)
if err == nil {
return f
}
}
return 0
}
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
odds, err := s.queries.GetPrematchOdds(ctx)
if err != nil {
@ -286,3 +273,34 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
return domainOdds, nil
}
func getString(v interface{}) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
func getConvertedFloat(v interface{}) float64 {
if s, ok := v.(string); ok {
f, err := strconv.ParseFloat(s, 64)
if err == nil {
return f
}
}
return 0
}
func getFloat(v interface{}) float64 {
if n, ok := v.(float64); ok {
return n
}
return 0
}
func getMap(v interface{}) map[string]interface{} {
if m, ok := v.(map[string]interface{}); ok {
return m
}
return nil
}

View File

@ -121,15 +121,11 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
if err != nil {
return domain.CreateBetOutcome{}, err
}
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
if err != nil {
return domain.CreateBetOutcome{}, err
}
newOutcome := domain.CreateBetOutcome{
EventID: eventID,
OddID: oddID,
MarketID: marketID,
SportID: sportID,
SportID: int64(event.SportID),
HomeTeamName: event.HomeTeam,
AwayTeamName: event.AwayTeam,
MarketName: odds.MarketName,
@ -287,7 +283,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return res, nil
}
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, sportID int32, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
var newOdds []domain.CreateBetOutcome
var totalOdds float32 = 1
@ -337,11 +333,6 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
s.logger.Error("Failed to parse odd", "error", err)
continue
}
sportID, err := strconv.ParseInt(sportID, 10, 64)
if err != nil {
s.logger.Error("Failed to get sport id", "error", err)
continue
}
eventID, err := strconv.ParseInt(eventID, 10, 64)
if err != nil {
s.logger.Error("Failed to get event id", "error", err)
@ -365,7 +356,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
EventID: eventID,
OddID: oddID,
MarketID: marketID,
SportID: sportID,
SportID: int64(sportID),
HomeTeamName: HomeTeam,
AwayTeamName: AwayTeam,
MarketName: marketName,
@ -388,7 +379,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
return newOdds, totalOdds, nil
}
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidString, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
// Get a unexpired event id

View File

@ -11,7 +11,7 @@ type Service interface {
FetchUpcomingEvents(ctx context.Context) error
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error)
GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error)
GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
// GetAndStoreMatchResult(ctx context.Context, eventID string) error

View File

@ -47,7 +47,6 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
s.fetchLiveEvents(ctx, url.name, url.source)
}()
}
wg.Wait()
return nil
}
@ -75,7 +74,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
events := []domain.Event{}
switch source {
case "bet365":
events = handleBet365prematch(body, sportID)
events = handleBet365prematch(body, sportID, source)
case "betfair":
events = handleBetfairprematch(body, sportID, source)
case "1xbet":
@ -97,7 +96,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
}
func handleBet365prematch(body []byte, sportID int) []domain.Event {
func handleBet365prematch(body []byte, sportID int, source string) []domain.Event {
var data struct {
Success int `json:"success"`
Results [][]map[string]interface{} `json:"results"`
@ -105,7 +104,7 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
return events
}
@ -117,24 +116,24 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
event := domain.Event{
ID: getString(ev["ID"]),
SportID: fmt.Sprintf("%d", sportID),
SportID: int32(sportID),
MatchName: getString(ev["NA"]),
Score: getString(ev["SS"]),
MatchMinute: getInt(ev["TM"]),
TimerStatus: getString(ev["TT"]),
HomeTeamID: getString(ev["HT"]),
AwayTeamID: getString(ev["AT"]),
HomeTeamID: getInt32(ev["HT"]),
AwayTeamID: getInt32(ev["AT"]),
HomeKitImage: getString(ev["K1"]),
AwayKitImage: getString(ev["K2"]),
LeagueName: getString(ev["CT"]),
LeagueID: getString(ev["C2"]),
LeagueID: getInt32(ev["C2"]),
LeagueCC: getString(ev["CB"]),
StartTime: time.Now().UTC().Format(time.RFC3339),
IsLive: true,
Status: "live",
MatchPeriod: getInt(ev["MD"]),
AddedTime: getInt(ev["TA"]),
Source: "bet365",
Source: source,
}
events = append(events, event)
@ -152,23 +151,20 @@ func handleBetfairprematch(body []byte, sportID int, source string) []domain.Eve
events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
return events
}
for _, ev := range data.Results {
homeRaw, _ := ev["home"].(map[string]interface{})
homeId, _ := homeRaw["id"].(string)
awayRaw, _ := ev["home"].(map[string]interface{})
awayId, _ := awayRaw["id"].(string)
event := domain.Event{
ID: getString(ev["id"]),
SportID: fmt.Sprintf("%d", sportID),
SportID: int32(sportID),
TimerStatus: getString(ev["time_status"]),
HomeTeamID: homeId,
AwayTeamID: awayId,
HomeTeamID: getInt32(homeRaw["id"]),
AwayTeamID: getInt32(awayRaw["id"]),
StartTime: time.Now().UTC().Format(time.RFC3339),
IsLive: true,
Status: "live",
@ -221,8 +217,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
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 sport %d at page %d", sportID, page)
url := fmt.Sprintf(url, sportID, s.token, page)
log.Printf("📡 Fetching data from %s - sport %d, for event data page %d", source, sportID, page)
resp, err := http.Get(url)
if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
@ -246,33 +242,35 @@ 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)
// _, 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
// }
// doesn't make sense to save and check back to back, but for now it can be here
s.store.SaveLeague(ctx, domain.League{
ID: leagueID,
Name: ev.League.Name,
IsActive: true,
})
if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
skippedLeague = append(skippedLeague, ev.League.Name)
continue
}
event := domain.UpcomingEvent{
ID: ev.ID,
SportID: ev.SportID,
SportID: convertInt32(ev.SportID),
MatchName: "",
HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID,
AwayTeamID: "",
HomeTeamID: convertInt32(ev.Home.ID),
AwayTeamID: 0,
HomeKitImage: "",
AwayKitImage: "",
LeagueID: ev.League.ID,
LeagueID: convertInt32(ev.League.ID),
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
@ -281,7 +279,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
if ev.Away != nil {
event.AwayTeam = ev.Away.Name
event.AwayTeamID = ev.Away.ID
event.AwayTeamID = convertInt32(ev.Away.ID)
event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
}
@ -319,6 +317,20 @@ func getInt(v interface{}) int {
}
return 0
}
func getInt32(v interface{}) int32 {
if n, err := strconv.Atoi(getString(v)); err == nil {
return int32(n)
}
return 0
}
func convertInt32(num string) int32 {
if n, err := strconv.Atoi(num); err == nil {
return int32(n)
}
return 0
}
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
return s.store.GetAllUpcomingEvents(ctx)
}
@ -327,7 +339,7 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
return s.store.GetExpiredUpcomingEvents(ctx)
}
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
}

View File

@ -10,6 +10,7 @@ import (
"log/slog"
"net/http"
"strconv"
"sync"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
@ -35,6 +36,38 @@ func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *Serv
// TODO Add the optimization to get 10 events at the same time
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
var wg sync.WaitGroup
errChan := make(chan error, 2)
// wg.Add(2)
wg.Add(1)
// go func() {
// defer wg.Done()
// if err := s.fetchBet365Odds(ctx); err != nil {
// errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
// }
// }()
go func() {
defer wg.Done()
if err := s.fetchBwinOdds(ctx); err != nil {
errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
}
}()
var errs []error
for err := range errChan {
errs = append(errs, err)
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
if err != nil {
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
@ -80,9 +113,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
continue
}
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
switch sportID {
switch event.SportID {
case domain.FOOTBALL:
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting football odd")
@ -147,6 +178,93 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
return nil
}
func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
// so instead of having event and odds fetched separetly event will also be fetched along with the odds
sportIds := []int{4, 12, 7}
for _, sportId := range sportIds {
url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
continue
}
resp, err := s.client.Do(req)
if err != nil {
log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
continue
}
var data struct {
Success int `json:"success"`
Results []map[string]interface{} `json:"results"`
}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf("Decode failed for sport_id=%d\nRaw: %s\n", sportId, string(body))
continue
}
for _, res := range data.Results {
if getInt(res["Id"]) == -1 {
continue
}
event := domain.Event{
ID: strconv.Itoa(getInt(res["Id"])),
SportID: int32(getInt(res["SportId"])),
LeagueID: int32(getInt(res["LeagueId"])),
LeagueName: getString(res["Leaguename"]),
HomeTeam: getString(res["HomeTeam"]),
HomeTeamID: int32(getInt(res["HomeTeamId"])),
AwayTeam: getString(res["AwayTeam"]),
AwayTeamID: int32(getInt(res["AwayTeamId"])),
StartTime: time.Now().UTC().Format(time.RFC3339),
TimerStatus: "1",
IsLive: true,
Status: "live",
Source: "bwin",
}
if err := s.store.SaveEvent(ctx, event); err != nil {
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
continue
}
for _, market := range []string{"Markets, optionMarkets"} {
for _, m := range getMapArray(res[market]) {
name := getMap(m["name"])
marketName := getString(name["value"])
market := domain.Market{
EventID: event.ID,
MarketID: getString(m["id"]),
MarketCategory: getString(m["category"]),
MarketName: marketName,
Source: "bwin",
}
results := getMapArray(m["results"])
market.Odds = results
s.store.SaveNonLiveMarket(ctx, market)
}
}
}
}
return nil
}
func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error {
var footballRes domain.FootballOddsResponse
if err := json.Unmarshal(res, &footballRes); err != nil {
@ -630,6 +748,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
continue
}
marketOdds, err := convertRawMessage(market.Odds)
if err != nil {
s.logger.Error("failed to conver json.RawMessage to []map[string]interface{} for market_id: ", market.ID)
errs = append(errs, err)
continue
}
marketRecord := domain.Market{
EventID: eventID,
FI: fi,
@ -638,7 +763,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
MarketName: market.Name,
MarketID: marketIDstr,
UpdatedAt: updatedAt,
Odds: market.Odds,
Odds: marketOdds,
// bwin won't reach this code so bet365 is hardcoded for now
Source: "bet365",
}
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
@ -679,3 +806,49 @@ func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingI
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
}
func getString(v interface{}) string {
if str, ok := v.(string); ok {
return str
}
return ""
}
func getInt(v interface{}) int {
if n, ok := v.(float64); ok {
return int(n)
}
return -1
}
func getMap(v interface{}) map[string]interface{} {
if m, ok := v.(map[string]interface{}); ok {
return m
}
return nil
}
func getMapArray(v interface{}) []map[string]interface{} {
result := []map[string]interface{}{}
if arr, ok := v.([]interface{}); ok {
for _, item := range arr {
if m, ok := item.(map[string]interface{}); ok {
result = append(result, m)
}
}
}
return result
}
func convertRawMessage(rawMessages []json.RawMessage) ([]map[string]interface{}, error) {
var result []map[string]interface{}
for _, raw := range rawMessages {
var m map[string]interface{}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, err
}
result = append(result, m)
}
return result, nil
}

View File

@ -80,14 +80,8 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
continue
}
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
if err != nil {
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
isDeleted = false
continue
}
// TODO: optimize this because the result is being fetched for each outcome which will have the same event id but different market id
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, sportID, outcome)
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, int64(event.SportID), outcome)
if err != nil {
if err == ErrEventIsNotActive {
s.logger.Warn("Event is not active", "event_id", outcome.EventID, "error", err)

View File

@ -71,18 +71,28 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
// role := c.Locals("role").(domain.Role)
leagueIDQuery := c.Query("league_id")
sportIDQuery := c.Query("sport_id")
leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
if err != nil {
h.logger.Error("invalid league id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
}
sportIDQuery, err := strconv.Atoi(c.Query("sport_id"))
if err != nil {
h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
}
firstStartTimeQuery := c.Query("first_start_time")
lastStartTimeQuery := c.Query("last_start_time")
leagueID := domain.ValidString{
Value: leagueIDQuery,
Valid: leagueIDQuery != "",
leagueID := domain.ValidInt32{
Value: int32(leagueIDQuery),
Valid: leagueIDQuery != 0,
}
sportID := domain.ValidString{
Value: sportIDQuery,
Valid: sportIDQuery != "",
sportID := domain.ValidInt32{
Value: int32(sportIDQuery),
Valid: sportIDQuery != 0,
}
var firstStartTime domain.ValidTime
@ -122,7 +132,6 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
}
var res domain.CreateBetRes
var err error
for i := 0; i < int(req.NumberOfBets); i++ {
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)

View File

@ -107,18 +107,27 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
leagueIDQuery := c.Query("league_id")
sportIDQuery := c.Query("sport_id")
leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
if err != nil {
h.logger.Error("invalid league id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
}
sportIDQuery, err := strconv.Atoi(c.Query("sport_id"))
if err != nil {
h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
}
firstStartTimeQuery := c.Query("first_start_time")
lastStartTimeQuery := c.Query("last_start_time")
leagueID := domain.ValidString{
Value: leagueIDQuery,
Valid: leagueIDQuery != "",
leagueID := domain.ValidInt32{
Value: int32(leagueIDQuery),
Valid: leagueIDQuery != 0,
}
sportID := domain.ValidString{
Value: sportIDQuery,
Valid: sportIDQuery != "",
sportID := domain.ValidInt32{
Value: int32(sportIDQuery),
Valid: sportIDQuery != 0,
}
var firstStartTime domain.ValidTime