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 refresh_tokens;
DROP TABLE IF EXISTS otps; DROP TABLE IF EXISTS otps;
DROP TABLE IF EXISTS odds; 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 ( CREATE TABLE events (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
sport_id TEXT, sport_id INT,
match_name TEXT, match_name TEXT,
home_team TEXT, home_team TEXT,
away_team TEXT, away_team TEXT,
home_team_id TEXT, home_team_id INT,
away_team_id TEXT, away_team_id INT,
home_kit_image TEXT, home_kit_image TEXT,
away_kit_image TEXT, away_kit_image TEXT,
league_id TEXT, league_id INT,
league_name TEXT, league_name TEXT,
league_cc TEXT, league_cc TEXT,
start_time TIMESTAMP, start_time TIMESTAMP,
@ -233,6 +233,13 @@ CREATE TABLE companies (
admin_id BIGINT NOT NULL, admin_id BIGINT NOT NULL,
wallet_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 -- Views
CREATE VIEW companies_details AS CREATE VIEW companies_details AS
SELECT companies.*, 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. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.28.0 // sqlc v1.29.0
// source: cashier.sql // source: cashier.sql
package dbgen package dbgen

View File

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

View File

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

View File

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

View File

@ -1,66 +1,9 @@
package domain package domain
// TODO Will make this dynamic by moving into the database type League struct {
ID int64
var SupportedLeagues = []int64{ Name string
// Football CountryCode string
10041282, //Premier League Bet365ID int32
10083364, //La Liga IsActive bool
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
} }

View File

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

View File

@ -9,7 +9,7 @@ type BaseResultResponse struct {
Results []json.RawMessage `json:"results"` Results []json.RawMessage `json:"results"`
} }
type League struct { type LeagueRes struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
CC string `json:"cc"` CC string `json:"cc"`
@ -28,14 +28,14 @@ type Score struct {
} }
type FootballResultResponse struct { type FootballResultResponse struct {
ID string `json:"id"` ID string `json:"id"`
SportID string `json:"sport_id"` SportID string `json:"sport_id"`
Time string `json:"time"` Time string `json:"time"`
TimeStatus string `json:"time_status"` TimeStatus string `json:"time_status"`
League League `json:"league"` League LeagueRes `json:"league"`
Home Team `json:"home"` Home Team `json:"home"`
Away Team `json:"away"` Away Team `json:"away"`
SS string `json:"ss"` SS string `json:"ss"`
Scores struct { Scores struct {
FirstHalf Score `json:"1"` FirstHalf Score `json:"1"`
SecondHalf Score `json:"2"` SecondHalf Score `json:"2"`
@ -67,14 +67,14 @@ type FootballResultResponse struct {
} }
type BasketballResultResponse struct { type BasketballResultResponse struct {
ID string `json:"id"` ID string `json:"id"`
SportID string `json:"sport_id"` SportID string `json:"sport_id"`
Time string `json:"time"` Time string `json:"time"`
TimeStatus string `json:"time_status"` TimeStatus string `json:"time_status"`
League League `json:"league"` League LeagueRes `json:"league"`
Home Team `json:"home"` Home Team `json:"home"`
Away Team `json:"away"` Away Team `json:"away"`
SS string `json:"ss"` SS string `json:"ss"`
Scores struct { Scores struct {
FirstQuarter Score `json:"1"` FirstQuarter Score `json:"1"`
SecondQuarter Score `json:"2"` SecondQuarter Score `json:"2"`
@ -114,14 +114,14 @@ type BasketballResultResponse struct {
Bet365ID string `json:"bet365_id"` Bet365ID string `json:"bet365_id"`
} }
type IceHockeyResultResponse struct { type IceHockeyResultResponse struct {
ID string `json:"id"` ID string `json:"id"`
SportID string `json:"sport_id"` SportID string `json:"sport_id"`
Time string `json:"time"` Time string `json:"time"`
TimeStatus string `json:"time_status"` TimeStatus string `json:"time_status"`
League League `json:"league"` League LeagueRes `json:"league"`
Home Team `json:"home"` Home Team `json:"home"`
Away Team `json:"away"` Away Team `json:"away"`
SS string `json:"ss"` SS string `json:"ss"`
Scores struct { Scores struct {
FirstPeriod Score `json:"1"` FirstPeriod Score `json:"1"`
SecondPeriod Score `json:"2"` 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{ return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{
ID: e.ID, 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}, MatchName: pgtype.Text{String: e.MatchName, Valid: true},
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true}, HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true}, AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true}, HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true}, AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true}, HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
AwayKitImage: pgtype.Text{String: e.AwayKitImage, 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}, LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true}, LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: parsedTime, 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 { func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{ return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
ID: e.ID, 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}, MatchName: pgtype.Text{String: e.MatchName, Valid: true},
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true}, HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true}, AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true}, HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true}, AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true}, HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
AwayKitImage: pgtype.Text{String: e.AwayKitImage, 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}, LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true}, LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: e.StartTime, 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 { for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{ upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID, ID: e.ID,
SportID: e.SportID.String, SportID: e.SportID.Int32,
MatchName: e.MatchName.String, MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String, HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String, AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String, HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.String, AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String, HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String, AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String, LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String, LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
@ -103,15 +103,15 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
for i, e := range events { for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{ upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID, ID: e.ID,
SportID: e.SportID.String, SportID: e.SportID.Int32,
MatchName: e.MatchName.String, MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String, HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String, AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String, HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.String, AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String, HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String, AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String, LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String, LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
@ -121,16 +121,16 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
return upcomingEvents, nil 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{ events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
LeagueID: pgtype.Text{ LeagueID: pgtype.Int4{
String: leagueID.Value, Int32: leagueID.Value,
Valid: leagueID.Valid, Valid: leagueID.Valid,
}, },
SportID: pgtype.Text{ SportID: pgtype.Int4{
String: sportID.Value, Int32: sportID.Value,
Valid: sportID.Valid, Valid: sportID.Valid,
}, },
Limit: pgtype.Int4{ Limit: pgtype.Int4{
Int32: int32(limit.Value), Int32: int32(limit.Value),
@ -157,15 +157,15 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.Val
for i, e := range events { for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{ upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID, ID: e.ID,
SportID: e.SportID.String, SportID: e.SportID.Int32,
MatchName: e.MatchName.String, MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String, HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String, AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String, HomeTeamID: e.HomeTeamID.Int32,
AwayTeamID: e.AwayTeamID.String, AwayTeamID: e.AwayTeamID.Int32,
HomeKitImage: e.HomeKitImage.String, HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String, AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String, LeagueID: e.LeagueID.Int32,
LeagueName: e.LeagueName.String, LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), 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{ totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
LeagueID: pgtype.Text{ LeagueID: pgtype.Int4{
String: leagueID.Value, Int32: leagueID.Value,
Valid: leagueID.Valid, Valid: leagueID.Valid,
}, },
SportID: pgtype.Text{ SportID: pgtype.Int4{
String: sportID.Value, Int32: sportID.Value,
Valid: sportID.Valid, Valid: sportID.Valid,
}, },
FirstStartTime: pgtype.Timestamp{ FirstStartTime: pgtype.Timestamp{
Time: firstStartTime.Value.UTC(), Time: firstStartTime.Value.UTC(),
@ -205,15 +205,15 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
return domain.UpcomingEvent{ return domain.UpcomingEvent{
ID: event.ID, ID: event.ID,
SportID: event.SportID.String, SportID: event.SportID.Int32,
MatchName: event.MatchName.String, MatchName: event.MatchName.String,
HomeTeam: event.HomeTeam.String, HomeTeam: event.HomeTeam.String,
AwayTeam: event.AwayTeam.String, AwayTeam: event.AwayTeam.String,
HomeTeamID: event.HomeTeamID.String, HomeTeamID: event.HomeTeamID.Int32,
AwayTeamID: event.AwayTeamID.String, AwayTeamID: event.AwayTeamID.Int32,
HomeKitImage: event.HomeKitImage.String, HomeKitImage: event.HomeKitImage.String,
AwayKitImage: event.AwayKitImage.String, AwayKitImage: event.AwayKitImage.String,
LeagueID: event.LeagueID.String, LeagueID: event.LeagueID.Int32,
LeagueName: event.LeagueName.String, LeagueName: event.LeagueName.String,
LeagueCC: event.LeagueCc.String, LeagueCC: event.LeagueCc.String,
StartTime: event.StartTime.Time.UTC(), 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 return nil
} }
for _, raw := range m.Odds { for _, item := range m.Odds {
var item map[string]interface{} var name string
if err := json.Unmarshal(raw, &item); err != nil { var oddsVal float64
continue
}
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"]) handicap := getString(item["handicap"])
oddsVal := getFloat(item["odds"])
rawOddsBytes, _ := json.Marshal(m.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}, Category: pgtype.Text{Valid: false},
RawOdds: rawOddsBytes, RawOdds: rawOddsBytes,
IsActive: pgtype.Bool{Bool: true, Valid: true}, 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}, FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
} }
@ -85,23 +89,6 @@ func writeFailedMarketLog(m domain.Market, err error) error {
return writeErr 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) { func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
odds, err := s.queries.GetPrematchOdds(ctx) odds, err := s.queries.GetPrematchOdds(ctx)
if err != nil { if err != nil {
@ -286,3 +273,34 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
return domainOdds, nil 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 { if err != nil {
return domain.CreateBetOutcome{}, err return domain.CreateBetOutcome{}, err
} }
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
if err != nil {
return domain.CreateBetOutcome{}, err
}
newOutcome := domain.CreateBetOutcome{ newOutcome := domain.CreateBetOutcome{
EventID: eventID, EventID: eventID,
OddID: oddID, OddID: oddID,
MarketID: marketID, MarketID: marketID,
SportID: sportID, SportID: int64(event.SportID),
HomeTeamName: event.HomeTeam, HomeTeamName: event.HomeTeam,
AwayTeamName: event.AwayTeam, AwayTeamName: event.AwayTeam,
MarketName: odds.MarketName, MarketName: odds.MarketName,
@ -287,7 +283,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return res, nil 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 newOdds []domain.CreateBetOutcome
var totalOdds float32 = 1 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) s.logger.Error("Failed to parse odd", "error", err)
continue 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) eventID, err := strconv.ParseInt(eventID, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Failed to get event id", "error", err) s.logger.Error("Failed to get event id", "error", err)
@ -365,7 +356,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
EventID: eventID, EventID: eventID,
OddID: oddID, OddID: oddID,
MarketID: marketID, MarketID: marketID,
SportID: sportID, SportID: int64(sportID),
HomeTeamName: HomeTeam, HomeTeamName: HomeTeam,
AwayTeamName: AwayTeam, AwayTeamName: AwayTeam,
MarketName: marketName, MarketName: marketName,
@ -388,7 +379,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
return newOdds, totalOdds, nil 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 // Get a unexpired event id

View File

@ -11,7 +11,7 @@ type Service interface {
FetchUpcomingEvents(ctx context.Context) error FetchUpcomingEvents(ctx context.Context) error
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetExpiredUpcomingEvents(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) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
// GetAndStoreMatchResult(ctx context.Context, eventID string) 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) s.fetchLiveEvents(ctx, url.name, url.source)
}() }()
} }
wg.Wait() wg.Wait()
return nil return nil
} }
@ -75,7 +74,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
events := []domain.Event{} events := []domain.Event{}
switch source { switch source {
case "bet365": case "bet365":
events = handleBet365prematch(body, sportID) events = handleBet365prematch(body, sportID, source)
case "betfair": case "betfair":
events = handleBetfairprematch(body, sportID, source) events = handleBetfairprematch(body, sportID, source)
case "1xbet": 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 { var data struct {
Success int `json:"success"` Success int `json:"success"`
Results [][]map[string]interface{} `json:"results"` Results [][]map[string]interface{} `json:"results"`
@ -105,7 +104,7 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
events := []domain.Event{} events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { 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 return events
} }
@ -117,24 +116,24 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
event := domain.Event{ event := domain.Event{
ID: getString(ev["ID"]), ID: getString(ev["ID"]),
SportID: fmt.Sprintf("%d", sportID), SportID: int32(sportID),
MatchName: getString(ev["NA"]), MatchName: getString(ev["NA"]),
Score: getString(ev["SS"]), Score: getString(ev["SS"]),
MatchMinute: getInt(ev["TM"]), MatchMinute: getInt(ev["TM"]),
TimerStatus: getString(ev["TT"]), TimerStatus: getString(ev["TT"]),
HomeTeamID: getString(ev["HT"]), HomeTeamID: getInt32(ev["HT"]),
AwayTeamID: getString(ev["AT"]), AwayTeamID: getInt32(ev["AT"]),
HomeKitImage: getString(ev["K1"]), HomeKitImage: getString(ev["K1"]),
AwayKitImage: getString(ev["K2"]), AwayKitImage: getString(ev["K2"]),
LeagueName: getString(ev["CT"]), LeagueName: getString(ev["CT"]),
LeagueID: getString(ev["C2"]), LeagueID: getInt32(ev["C2"]),
LeagueCC: getString(ev["CB"]), LeagueCC: getString(ev["CB"]),
StartTime: time.Now().UTC().Format(time.RFC3339), StartTime: time.Now().UTC().Format(time.RFC3339),
IsLive: true, IsLive: true,
Status: "live", Status: "live",
MatchPeriod: getInt(ev["MD"]), MatchPeriod: getInt(ev["MD"]),
AddedTime: getInt(ev["TA"]), AddedTime: getInt(ev["TA"]),
Source: "bet365", Source: source,
} }
events = append(events, event) events = append(events, event)
@ -152,23 +151,20 @@ func handleBetfairprematch(body []byte, sportID int, source string) []domain.Eve
events := []domain.Event{} events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { 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 return events
} }
for _, ev := range data.Results { for _, ev := range data.Results {
homeRaw, _ := ev["home"].(map[string]interface{}) homeRaw, _ := ev["home"].(map[string]interface{})
homeId, _ := homeRaw["id"].(string)
awayRaw, _ := ev["home"].(map[string]interface{}) awayRaw, _ := ev["home"].(map[string]interface{})
awayId, _ := awayRaw["id"].(string)
event := domain.Event{ event := domain.Event{
ID: getString(ev["id"]), ID: getString(ev["id"]),
SportID: fmt.Sprintf("%d", sportID), SportID: int32(sportID),
TimerStatus: getString(ev["time_status"]), TimerStatus: getString(ev["time_status"]),
HomeTeamID: homeId, HomeTeamID: getInt32(homeRaw["id"]),
AwayTeamID: awayId, AwayTeamID: getInt32(awayRaw["id"]),
StartTime: time.Now().UTC().Format(time.RFC3339), StartTime: time.Now().UTC().Format(time.RFC3339),
IsLive: true, IsLive: true,
Status: "live", Status: "live",
@ -221,8 +217,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
log.Printf("Sport ID %d", sportID) log.Printf("Sport ID %d", sportID)
for page <= totalPages { for page <= totalPages {
page = page + 1 page = page + 1
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page) url := fmt.Sprintf(url, sportID, s.token, page)
log.Printf("📡 Fetching data for sport %d at page %d", sportID, page) log.Printf("📡 Fetching data from %s - sport %d, for event data page %d", source, sportID, page)
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err) 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 // continue
// } // }
// leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64) leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
// if err != nil { if err != nil {
// log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID) log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
// continue continue
// } }
// if !slices.Contains(domain.SupportedLeagues, leagueID) { // doesn't make sense to save and check back to back, but for now it can be here
// // fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID) s.store.SaveLeague(ctx, domain.League{
// _, err = fmt.Fprintf(b, "Skipped league %s (%d) in sport %d\n", ev.League.Name, leagueID, sportID) ID: leagueID,
// if err != nil { Name: ev.League.Name,
// fmt.Printf(" Error while logging skipped league") IsActive: true,
// } })
// skippedLeague = append(skippedLeague, ev.League.Name)
// continue if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
// } skippedLeague = append(skippedLeague, ev.League.Name)
continue
}
event := domain.UpcomingEvent{ event := domain.UpcomingEvent{
ID: ev.ID, ID: ev.ID,
SportID: ev.SportID, SportID: convertInt32(ev.SportID),
MatchName: "", MatchName: "",
HomeTeam: ev.Home.Name, HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID, HomeTeamID: convertInt32(ev.Home.ID),
AwayTeamID: "", AwayTeamID: 0,
HomeKitImage: "", HomeKitImage: "",
AwayKitImage: "", AwayKitImage: "",
LeagueID: ev.League.ID, LeagueID: convertInt32(ev.League.ID),
LeagueName: ev.League.Name, LeagueName: ev.League.Name,
LeagueCC: "", LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(), StartTime: time.Unix(startUnix, 0).UTC(),
@ -281,7 +279,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
if ev.Away != nil { if ev.Away != nil {
event.AwayTeam = ev.Away.Name 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 event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
} }
@ -319,6 +317,20 @@ func getInt(v interface{}) int {
} }
return 0 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) { func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
return s.store.GetAllUpcomingEvents(ctx) return s.store.GetAllUpcomingEvents(ctx)
} }
@ -327,7 +339,7 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
return s.store.GetExpiredUpcomingEvents(ctx) 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) return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
} }

View File

@ -10,6 +10,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "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 // TODO Add the optimization to get 10 events at the same time
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { 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) eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
if err != nil { if err != nil {
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err) log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
@ -80,9 +113,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
continue continue
} }
sportID, err := strconv.ParseInt(event.SportID, 10, 64) switch event.SportID {
switch sportID {
case domain.FOOTBALL: case domain.FOOTBALL:
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil { if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting football odd") s.logger.Error("Error while inserting football odd")
@ -147,6 +178,93 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
return nil 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 { func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error {
var footballRes domain.FootballOddsResponse var footballRes domain.FootballOddsResponse
if err := json.Unmarshal(res, &footballRes); err != nil { if err := json.Unmarshal(res, &footballRes); err != nil {
@ -630,6 +748,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
continue 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{ marketRecord := domain.Market{
EventID: eventID, EventID: eventID,
FI: fi, FI: fi,
@ -638,7 +763,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
MarketName: market.Name, MarketName: market.Name,
MarketID: marketIDstr, MarketID: marketIDstr,
UpdatedAt: updatedAt, 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) 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) { func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset) 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 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 // 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 != nil {
if err == ErrEventIsNotActive { if err == ErrEventIsNotActive {
s.logger.Warn("Event is not active", "event_id", outcome.EventID, "error", err) 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) userID := c.Locals("user_id").(int64)
// role := c.Locals("role").(domain.Role) // role := c.Locals("role").(domain.Role)
leagueIDQuery := c.Query("league_id") leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
sportIDQuery := c.Query("sport_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") firstStartTimeQuery := c.Query("first_start_time")
lastStartTimeQuery := c.Query("last_start_time") lastStartTimeQuery := c.Query("last_start_time")
leagueID := domain.ValidString{ leagueID := domain.ValidInt32{
Value: leagueIDQuery, Value: int32(leagueIDQuery),
Valid: leagueIDQuery != "", Valid: leagueIDQuery != 0,
} }
sportID := domain.ValidString{ sportID := domain.ValidInt32{
Value: sportIDQuery, Value: int32(sportIDQuery),
Valid: sportIDQuery != "", Valid: sportIDQuery != 0,
} }
var firstStartTime domain.ValidTime var firstStartTime domain.ValidTime
@ -122,7 +132,6 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
} }
var res domain.CreateBetRes var res domain.CreateBetRes
var err error
for i := 0; i < int(req.NumberOfBets); i++ { for i := 0; i < int(req.NumberOfBets); i++ {
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime) 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 { func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1) page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10) pageSize := c.QueryInt("page_size", 10)
leagueIDQuery := c.Query("league_id") leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
sportIDQuery := c.Query("sport_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") firstStartTimeQuery := c.Query("first_start_time")
lastStartTimeQuery := c.Query("last_start_time") lastStartTimeQuery := c.Query("last_start_time")
leagueID := domain.ValidString{ leagueID := domain.ValidInt32{
Value: leagueIDQuery, Value: int32(leagueIDQuery),
Valid: leagueIDQuery != "", Valid: leagueIDQuery != 0,
} }
sportID := domain.ValidString{ sportID := domain.ValidInt32{
Value: sportIDQuery, Value: int32(sportIDQuery),
Valid: sportIDQuery != "", Valid: sportIDQuery != 0,
} }
var firstStartTime domain.ValidTime var firstStartTime domain.ValidTime