events pagination + ticket and bet validation

This commit is contained in:
Samuel Tariku 2025-04-22 03:20:52 +03:00
parent cab7dbe2fa
commit 991199c3dc
33 changed files with 873 additions and 384 deletions

View File

@ -76,6 +76,8 @@ CREATE TABLE IF NOT EXISTS bet_outcomes (
market_name VARCHAR(255) NOT NULL, market_name VARCHAR(255) NOT NULL,
odd REAL NOT NULL, odd REAL NOT NULL,
odd_name VARCHAR(255) NOT NULL, odd_name VARCHAR(255) NOT NULL,
odd_header VARCHAR(255) NOT NULL,
odd_handicap VARCHAR(255) NOT NULL,
expires TIMESTAMP NOT NULL expires TIMESTAMP NOT NULL
); );
CREATE TABLE IF NOT EXISTS ticket_outcomes ( CREATE TABLE IF NOT EXISTS ticket_outcomes (
@ -89,6 +91,8 @@ CREATE TABLE IF NOT EXISTS ticket_outcomes (
market_name VARCHAR(255) NOT NULL, market_name VARCHAR(255) NOT NULL,
odd REAL NOT NULL, odd REAL NOT NULL,
odd_name VARCHAR(255) NOT NULL, odd_name VARCHAR(255) NOT NULL,
odd_header VARCHAR(255) NOT NULL,
odd_handicap VARCHAR(255) NOT NULL,
expires TIMESTAMP NOT NULL expires TIMESTAMP NOT NULL
); );
CREATE VIEW bet_with_outcomes AS CREATE VIEW bet_with_outcomes AS
@ -321,6 +325,34 @@ VALUES (
NULL, NULL,
FALSE FALSE
); );
INSERT INTO users (
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended_at,
suspended
)
VALUES (
'Kirubel',
'Kibru',
'kirubeljkl679 @gmail.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE
);
INSERT INTO supported_operations (name, description) INSERT INTO supported_operations (name, description)
VALUES ('SportBook', 'Sportbook operations'), VALUES ('SportBook', 'Sportbook operations'),
('Virtual', 'Virtual operations'), ('Virtual', 'Virtual operations'),

View File

@ -23,9 +23,11 @@ INSERT INTO bet_outcomes (
market_name, market_name,
odd, odd,
odd_name, odd_name,
odd_header,
odd_handicap,
expires expires
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
-- name: GetAllBets :many -- name: GetAllBets :many
SELECT * SELECT *
FROM bet_with_outcomes; FROM bet_with_outcomes;
@ -46,6 +48,11 @@ UPDATE bets
SET cashed_out = $2, SET cashed_out = $2,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1; WHERE id = $1;
-- name: UpdateStatus :exec
UPDATE bets
SET status = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1;
-- name: DeleteBet :exec -- name: DeleteBet :exec
DELETE FROM bets DELETE FROM bets
WHERE id = $1; WHERE id = $1;

View File

@ -1,19 +1,50 @@
-- name: InsertEvent :exec -- name: InsertEvent :exec
INSERT INTO events ( INSERT INTO events (
id, sport_id, match_name, home_team, away_team, id,
home_team_id, away_team_id, home_kit_image, away_kit_image, sport_id,
league_id, league_name, league_cc, start_time, score, match_name,
match_minute, timer_status, added_time, match_period, home_team,
is_live, status away_team,
) VALUES ( home_team_id,
$1, $2, $3, $4, $5, away_team_id,
$6, $7, $8, $9, home_kit_image,
$10, $11, $12, $13, $14, away_kit_image,
$15, $16, $17, $18, league_id,
$19, $20 league_name,
) league_cc,
ON CONFLICT (id) DO UPDATE SET start_time,
sport_id = EXCLUDED.sport_id, score,
match_minute,
timer_status,
added_time,
match_period,
is_live,
status
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15,
$16,
$17,
$18,
$19,
$20
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name, match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team, home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team, away_team = EXCLUDED.away_team,
@ -35,18 +66,41 @@ ON CONFLICT (id) DO UPDATE SET
fetched_at = now(); fetched_at = now();
-- name: InsertUpcomingEvent :exec -- name: InsertUpcomingEvent :exec
INSERT INTO events ( INSERT INTO events (
id, sport_id, match_name, home_team, away_team, id,
home_team_id, away_team_id, home_kit_image, away_kit_image, sport_id,
league_id, league_name, league_cc, start_time, match_name,
is_live, status home_team,
) VALUES ( away_team,
$1, $2, $3, $4, $5, home_team_id,
$6, $7, $8, $9, away_team_id,
$10, $11, $12, $13, home_kit_image,
false, 'upcoming' away_kit_image,
) league_id,
ON CONFLICT (id) DO UPDATE SET league_name,
sport_id = EXCLUDED.sport_id, league_cc,
start_time,
is_live,
status
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
false,
'upcoming'
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name, match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team, home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team, away_team = EXCLUDED.away_team,
@ -61,14 +115,12 @@ ON CONFLICT (id) DO UPDATE SET
is_live = false, is_live = false,
status = 'upcoming', status = 'upcoming',
fetched_at = now(); fetched_at = now();
-- name: ListLiveEvents :many -- name: ListLiveEvents :many
SELECT id FROM events WHERE is_live = true; SELECT id
FROM events
WHERE is_live = true;
-- name: GetAllUpcomingEvents :many -- name: GetAllUpcomingEvents :many
SELECT SELECT id,
id,
sport_id, sport_id,
match_name, match_name,
home_team, home_team,
@ -86,11 +138,37 @@ SELECT
fetched_at fetched_at
FROM events FROM events
WHERE is_live = false WHERE is_live = false
AND status = 'upcoming' AND status = 'upcoming'
ORDER BY start_time ASC; ORDER BY start_time ASC;
-- name: GetTotalEvents :one
SELECT COUNT(*)
FROM events
WHERE is_live = false
AND status = 'upcoming';
-- name: GetPaginatedUpcomingEvents :many
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
fetched_at
FROM events
WHERE is_live = false
AND status = 'upcoming'
ORDER BY start_time ASC
LIMIT $1 OFFSET $2;
-- name: GetUpcomingByID :one -- name: GetUpcomingByID :one
SELECT SELECT id,
id,
sport_id, sport_id,
match_name, match_name,
home_team, home_team,
@ -108,6 +186,6 @@ SELECT
fetched_at fetched_at
FROM events FROM events
WHERE id = $1 WHERE id = $1
AND is_live = false AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
LIMIT 1; LIMIT 1;

View File

@ -83,16 +83,18 @@ SELECT event_id,
FROM odds FROM odds
WHERE is_active = true WHERE is_active = true
AND source = 'b365api'; AND source = 'b365api';
-- name: GetRawOddsByMarketID :many -- name: GetRawOddsByMarketID :one
SELECT id, SELECT id,
market_name,
handicap,
raw_odds, raw_odds,
fetched_at fetched_at
FROM odds FROM odds
WHERE market_id = $1 WHERE market_id = $1
AND fi = $2 AND fi = $2
AND is_active = true AND is_active = true
AND source = 'b365api' AND source = 'b365api';
LIMIT $3 OFFSET $4;
-- name: GetPrematchOddsByUpcomingID :many -- name: GetPrematchOddsByUpcomingID :many
SELECT o.event_id, SELECT o.event_id,
o.fi, o.fi,

View File

@ -13,9 +13,24 @@ INSERT INTO ticket_outcomes (
market_name, market_name,
odd, odd,
odd_name, odd_name,
odd_header,
odd_handicap,
expires expires
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12
);
-- name: GetAllTickets :many -- name: GetAllTickets :many
SELECT * SELECT *
FROM ticket_with_outcomes; FROM ticket_with_outcomes;

View File

@ -1303,6 +1303,20 @@ const docTemplate = `{
"prematch" "prematch"
], ],
"summary": "Retrieve all upcoming events", "summary": "Retrieve all upcoming events",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -2667,6 +2681,14 @@ const docTemplate = `{
"type": "number", "type": "number",
"example": 1.5 "example": 1.5
}, },
"odd_handicap": {
"type": "string",
"example": "1"
},
"odd_header": {
"type": "string",
"example": "1"
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
@ -2764,9 +2786,15 @@ const docTemplate = `{
"fetched_at": { "fetched_at": {
"type": "string" "type": "string"
}, },
"handicap": {
"type": "string"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"market_name": {
"type": "string"
},
"raw_odds": { "raw_odds": {
"type": "array", "type": "array",
"items": {} "items": {}
@ -2825,6 +2853,14 @@ const docTemplate = `{
"type": "number", "type": "number",
"example": 1.5 "example": 1.5
}, },
"odd_handicap": {
"type": "string",
"example": "1"
},
"odd_header": {
"type": "string",
"example": "1"
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
@ -3069,45 +3105,18 @@ const docTemplate = `{
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": { "event_id": {
"description": "BetID int64 ` + "`" + `json:\"bet_id\" example:\"1\"` + "`" + `",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": { "market_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
},
"odd_name": {
"type": "string",
"example": "1"
} }
} }
}, },
@ -3264,45 +3273,18 @@ const docTemplate = `{
"handlers.CreateTicketOutcomeReq": { "handlers.CreateTicketOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": { "event_id": {
"description": "TicketID int64 ` + "`" + `json:\"ticket_id\" example:\"1\"` + "`" + `",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": { "market_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
} }
} }
}, },
@ -3858,11 +3840,17 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"metadata": {}, "metadata": {},
"page": {
"type": "integer"
},
"status": { "status": {
"$ref": "#/definitions/response.Status" "$ref": "#/definitions/response.Status"
}, },
"timestamp": { "timestamp": {
"type": "string" "type": "string"
},
"total": {
"type": "integer"
} }
} }
}, },

View File

@ -1295,6 +1295,20 @@
"prematch" "prematch"
], ],
"summary": "Retrieve all upcoming events", "summary": "Retrieve all upcoming events",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -2659,6 +2673,14 @@
"type": "number", "type": "number",
"example": 1.5 "example": 1.5
}, },
"odd_handicap": {
"type": "string",
"example": "1"
},
"odd_header": {
"type": "string",
"example": "1"
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
@ -2756,9 +2778,15 @@
"fetched_at": { "fetched_at": {
"type": "string" "type": "string"
}, },
"handicap": {
"type": "string"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"market_name": {
"type": "string"
},
"raw_odds": { "raw_odds": {
"type": "array", "type": "array",
"items": {} "items": {}
@ -2817,6 +2845,14 @@
"type": "number", "type": "number",
"example": 1.5 "example": 1.5
}, },
"odd_handicap": {
"type": "string",
"example": "1"
},
"odd_header": {
"type": "string",
"example": "1"
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
@ -3061,45 +3097,18 @@
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": { "event_id": {
"description": "BetID int64 `json:\"bet_id\" example:\"1\"`",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": { "market_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
},
"odd_name": {
"type": "string",
"example": "1"
} }
} }
}, },
@ -3256,45 +3265,18 @@
"handlers.CreateTicketOutcomeReq": { "handlers.CreateTicketOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": { "event_id": {
"description": "TicketID int64 `json:\"ticket_id\" example:\"1\"`",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": { "market_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": { "odd_id": {
"type": "integer", "type": "integer",
"example": 1 "example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
} }
} }
}, },
@ -3850,11 +3832,17 @@
"type": "string" "type": "string"
}, },
"metadata": {}, "metadata": {},
"page": {
"type": "integer"
},
"status": { "status": {
"$ref": "#/definitions/response.Status" "$ref": "#/definitions/response.Status"
}, },
"timestamp": { "timestamp": {
"type": "string" "type": "string"
},
"total": {
"type": "integer"
} }
} }
}, },

View File

@ -28,6 +28,12 @@ definitions:
odd: odd:
example: 1.5 example: 1.5
type: number type: number
odd_handicap:
example: "1"
type: string
odd_header:
example: "1"
type: string
odd_id: odd_id:
example: 1 example: 1
type: integer type: integer
@ -97,8 +103,12 @@ definitions:
properties: properties:
fetched_at: fetched_at:
type: string type: string
handicap:
type: string
id: id:
type: integer type: integer
market_name:
type: string
raw_odds: raw_odds:
items: {} items: {}
type: array type: array
@ -143,6 +153,12 @@ definitions:
odd: odd:
example: 1.5 example: 1.5
type: number type: number
odd_handicap:
example: "1"
type: string
odd_header:
example: "1"
type: string
odd_id: odd_id:
example: 1 example: 1
type: integer type: integer
@ -317,36 +333,16 @@ definitions:
type: object type: object
handlers.CreateBetOutcomeReq: handlers.CreateBetOutcomeReq:
properties: properties:
away_team_name:
example: Liverpool
type: string
bet_id:
example: 1
type: integer
event_id: event_id:
description: BetID int64 `json:"bet_id" example:"1"`
example: 1 example: 1
type: integer type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
market_id: market_id:
example: 1 example: 1
type: integer type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id: odd_id:
example: 1 example: 1
type: integer type: integer
odd_name:
example: "1"
type: string
type: object type: object
handlers.CreateBetReq: handlers.CreateBetReq:
properties: properties:
@ -455,36 +451,16 @@ definitions:
type: object type: object
handlers.CreateTicketOutcomeReq: handlers.CreateTicketOutcomeReq:
properties: properties:
away_team_name:
example: Liverpool
type: string
event_id: event_id:
description: TicketID int64 `json:"ticket_id" example:"1"`
example: 1 example: 1
type: integer type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
market_id: market_id:
example: 1 example: 1
type: integer type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id: odd_id:
example: 1 example: 1
type: integer type: integer
odd_name:
example: "1"
type: string
ticket_id:
example: 1
type: integer
type: object type: object
handlers.CreateTicketReq: handlers.CreateTicketReq:
properties: properties:
@ -867,10 +843,14 @@ definitions:
message: message:
type: string type: string
metadata: {} metadata: {}
page:
type: integer
status: status:
$ref: '#/definitions/response.Status' $ref: '#/definitions/response.Status'
timestamp: timestamp:
type: string type: string
total:
type: integer
type: object type: object
response.Status: response.Status:
enum: enum:
@ -1732,6 +1712,15 @@ paths:
consumes: consumes:
- application/json - application/json
description: Retrieve all upcoming events from the database description: Retrieve all upcoming events from the database
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: page_size
type: integer
produces: produces:
- application/json - application/json
responses: responses:

View File

@ -80,6 +80,8 @@ type CreateBetOutcomeParams struct {
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
Odd float32 `json:"odd"` Odd float32 `json:"odd"`
OddName string `json:"odd_name"` OddName string `json:"odd_name"`
OddHeader string `json:"odd_header"`
OddHandicap string `json:"odd_handicap"`
Expires pgtype.Timestamp `json:"expires"` Expires pgtype.Timestamp `json:"expires"`
} }
@ -256,3 +258,20 @@ func (q *Queries) UpdateCashOut(ctx context.Context, arg UpdateCashOutParams) er
_, err := q.db.Exec(ctx, UpdateCashOut, arg.ID, arg.CashedOut) _, err := q.db.Exec(ctx, UpdateCashOut, arg.ID, arg.CashedOut)
return err return err
} }
const UpdateStatus = `-- name: UpdateStatus :exec
UPDATE bets
SET status = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
`
type UpdateStatusParams struct {
ID int64 `json:"id"`
Status int32 `json:"status"`
}
func (q *Queries) UpdateStatus(ctx context.Context, arg UpdateStatusParams) error {
_, err := q.db.Exec(ctx, UpdateStatus, arg.ID, arg.Status)
return err
}

View File

@ -38,6 +38,8 @@ func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) {
r.rows[0].MarketName, r.rows[0].MarketName,
r.rows[0].Odd, r.rows[0].Odd,
r.rows[0].OddName, r.rows[0].OddName,
r.rows[0].OddHeader,
r.rows[0].OddHandicap,
r.rows[0].Expires, r.rows[0].Expires,
}, nil }, nil
} }
@ -47,7 +49,7 @@ func (r iteratorForCreateBetOutcome) Err() error {
} }
func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) { func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) {
return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"bet_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "expires"}, &iteratorForCreateBetOutcome{rows: arg}) return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"bet_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "odd_header", "odd_handicap", "expires"}, &iteratorForCreateBetOutcome{rows: arg})
} }
// iteratorForCreateTicketOutcome implements pgx.CopyFromSource. // iteratorForCreateTicketOutcome implements pgx.CopyFromSource.
@ -79,6 +81,8 @@ func (r iteratorForCreateTicketOutcome) Values() ([]interface{}, error) {
r.rows[0].MarketName, r.rows[0].MarketName,
r.rows[0].Odd, r.rows[0].Odd,
r.rows[0].OddName, r.rows[0].OddName,
r.rows[0].OddHeader,
r.rows[0].OddHandicap,
r.rows[0].Expires, r.rows[0].Expires,
}, nil }, nil
} }
@ -88,5 +92,5 @@ func (r iteratorForCreateTicketOutcome) Err() error {
} }
func (q *Queries) CreateTicketOutcome(ctx context.Context, arg []CreateTicketOutcomeParams) (int64, error) { func (q *Queries) CreateTicketOutcome(ctx context.Context, arg []CreateTicketOutcomeParams) (int64, error) {
return q.db.CopyFrom(ctx, []string{"ticket_outcomes"}, []string{"ticket_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "expires"}, &iteratorForCreateTicketOutcome{rows: arg}) return q.db.CopyFrom(ctx, []string{"ticket_outcomes"}, []string{"ticket_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "odd_header", "odd_handicap", "expires"}, &iteratorForCreateTicketOutcome{rows: arg})
} }

View File

@ -12,8 +12,7 @@ import (
) )
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
SELECT SELECT id,
id,
sport_id, sport_id,
match_name, match_name,
home_team, home_team,
@ -31,7 +30,7 @@ SELECT
fetched_at fetched_at
FROM events FROM events
WHERE is_live = false WHERE is_live = false
AND status = 'upcoming' AND status = 'upcoming'
ORDER BY start_time ASC ORDER BY start_time ASC
` `
@ -91,9 +90,107 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve
return items, nil return items, nil
} }
const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
fetched_at
FROM events
WHERE is_live = false
AND status = 'upcoming'
ORDER BY start_time ASC
LIMIT $1 OFFSET $2
`
type GetPaginatedUpcomingEventsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
type GetPaginatedUpcomingEventsRow struct {
ID string `json:"id"`
SportID pgtype.Text `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"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Text `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetPaginatedUpcomingEventsRow
for rows.Next() {
var i GetPaginatedUpcomingEventsRow
if err := rows.Scan(
&i.ID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.IsLive,
&i.Status,
&i.FetchedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetTotalEvents = `-- name: GetTotalEvents :one
SELECT COUNT(*)
FROM events
WHERE is_live = false
AND status = 'upcoming'
`
func (q *Queries) GetTotalEvents(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalEvents)
var count int64
err := row.Scan(&count)
return count, err
}
const GetUpcomingByID = `-- name: GetUpcomingByID :one const GetUpcomingByID = `-- name: GetUpcomingByID :one
SELECT SELECT id,
id,
sport_id, sport_id,
match_name, match_name,
home_team, home_team,
@ -111,8 +208,8 @@ SELECT
fetched_at fetched_at
FROM events FROM events
WHERE id = $1 WHERE id = $1
AND is_live = false AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
LIMIT 1 LIMIT 1
` `
@ -161,20 +258,51 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingBy
const InsertEvent = `-- name: InsertEvent :exec const InsertEvent = `-- name: InsertEvent :exec
INSERT INTO events ( INSERT INTO events (
id, sport_id, match_name, home_team, away_team, id,
home_team_id, away_team_id, home_kit_image, away_kit_image, sport_id,
league_id, league_name, league_cc, start_time, score, match_name,
match_minute, timer_status, added_time, match_period, home_team,
is_live, status away_team,
) VALUES ( home_team_id,
$1, $2, $3, $4, $5, away_team_id,
$6, $7, $8, $9, home_kit_image,
$10, $11, $12, $13, $14, away_kit_image,
$15, $16, $17, $18, league_id,
$19, $20 league_name,
) league_cc,
ON CONFLICT (id) DO UPDATE SET start_time,
sport_id = EXCLUDED.sport_id, score,
match_minute,
timer_status,
added_time,
match_period,
is_live,
status
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15,
$16,
$17,
$18,
$19,
$20
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name, match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team, home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team, away_team = EXCLUDED.away_team,
@ -247,18 +375,41 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error
const InsertUpcomingEvent = `-- name: InsertUpcomingEvent :exec const InsertUpcomingEvent = `-- name: InsertUpcomingEvent :exec
INSERT INTO events ( INSERT INTO events (
id, sport_id, match_name, home_team, away_team, id,
home_team_id, away_team_id, home_kit_image, away_kit_image, sport_id,
league_id, league_name, league_cc, start_time, match_name,
is_live, status home_team,
) VALUES ( away_team,
$1, $2, $3, $4, $5, home_team_id,
$6, $7, $8, $9, away_team_id,
$10, $11, $12, $13, home_kit_image,
false, 'upcoming' away_kit_image,
) league_id,
ON CONFLICT (id) DO UPDATE SET league_name,
sport_id = EXCLUDED.sport_id, league_cc,
start_time,
is_live,
status
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
false,
'upcoming'
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name, match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team, home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team, away_team = EXCLUDED.away_team,
@ -311,7 +462,9 @@ func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEve
} }
const ListLiveEvents = `-- name: ListLiveEvents :many const ListLiveEvents = `-- name: ListLiveEvents :many
SELECT id FROM events WHERE is_live = true SELECT id
FROM events
WHERE is_live = true
` `
func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) { func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) {

View File

@ -35,6 +35,8 @@ type BetOutcome struct {
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
Odd float32 `json:"odd"` Odd float32 `json:"odd"`
OddName string `json:"odd_name"` OddName string `json:"odd_name"`
OddHeader string `json:"odd_header"`
OddHandicap string `json:"odd_handicap"`
Expires pgtype.Timestamp `json:"expires"` Expires pgtype.Timestamp `json:"expires"`
} }
@ -211,6 +213,8 @@ type TicketOutcome struct {
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
Odd float32 `json:"odd"` Odd float32 `json:"odd"`
OddName string `json:"odd_name"` OddName string `json:"odd_name"`
OddHeader string `json:"odd_header"`
OddHandicap string `json:"odd_handicap"`
Expires pgtype.Timestamp `json:"expires"` Expires pgtype.Timestamp `json:"expires"`
} }

View File

@ -247,8 +247,10 @@ func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPremat
return items, nil return items, nil
} }
const GetRawOddsByMarketID = `-- name: GetRawOddsByMarketID :many const GetRawOddsByMarketID = `-- name: GetRawOddsByMarketID :one
SELECT id, SELECT id,
market_name,
handicap,
raw_odds, raw_odds,
fetched_at fetched_at
FROM odds FROM odds
@ -256,45 +258,32 @@ WHERE market_id = $1
AND fi = $2 AND fi = $2
AND is_active = true AND is_active = true
AND source = 'b365api' AND source = 'b365api'
LIMIT $3 OFFSET $4
` `
type GetRawOddsByMarketIDParams struct { type GetRawOddsByMarketIDParams struct {
MarketID pgtype.Text `json:"market_id"` MarketID pgtype.Text `json:"market_id"`
Fi pgtype.Text `json:"fi"` Fi pgtype.Text `json:"fi"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
} }
type GetRawOddsByMarketIDRow struct { type GetRawOddsByMarketIDRow struct {
ID int32 `json:"id"` ID int32 `json:"id"`
RawOdds []byte `json:"raw_odds"` MarketName pgtype.Text `json:"market_name"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` Handicap pgtype.Text `json:"handicap"`
RawOdds []byte `json:"raw_odds"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
} }
func (q *Queries) GetRawOddsByMarketID(ctx context.Context, arg GetRawOddsByMarketIDParams) ([]GetRawOddsByMarketIDRow, error) { func (q *Queries) GetRawOddsByMarketID(ctx context.Context, arg GetRawOddsByMarketIDParams) (GetRawOddsByMarketIDRow, error) {
rows, err := q.db.Query(ctx, GetRawOddsByMarketID, row := q.db.QueryRow(ctx, GetRawOddsByMarketID, arg.MarketID, arg.Fi)
arg.MarketID, var i GetRawOddsByMarketIDRow
arg.Fi, err := row.Scan(
arg.Limit, &i.ID,
arg.Offset, &i.MarketName,
&i.Handicap,
&i.RawOdds,
&i.FetchedAt,
) )
if err != nil { return i, err
return nil, err
}
defer rows.Close()
var items []GetRawOddsByMarketIDRow
for rows.Next() {
var i GetRawOddsByMarketIDRow
if err := rows.Scan(&i.ID, &i.RawOdds, &i.FetchedAt); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
} }
const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec

View File

@ -45,6 +45,8 @@ type CreateTicketOutcomeParams struct {
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
Odd float32 `json:"odd"` Odd float32 `json:"odd"`
OddName string `json:"odd_name"` OddName string `json:"odd_name"`
OddHeader string `json:"odd_header"`
OddHandicap string `json:"odd_handicap"`
Expires pgtype.Timestamp `json:"expires"` Expires pgtype.Timestamp `json:"expires"`
} }
@ -131,7 +133,7 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcom
} }
const GetTicketOutcome = `-- name: GetTicketOutcome :many const GetTicketOutcome = `-- name: GetTicketOutcome :many
SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, expires SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, expires
FROM ticket_outcomes FROM ticket_outcomes
WHERE ticket_id = $1 WHERE ticket_id = $1
` `
@ -156,6 +158,8 @@ func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]Ticke
&i.MarketName, &i.MarketName,
&i.Odd, &i.Odd,
&i.OddName, &i.OddName,
&i.OddHeader,
&i.OddHandicap,
&i.Expires, &i.Expires,
); err != nil { ); err != nil {
return nil, err return nil, err

View File

@ -3,17 +3,20 @@ package domain
import "time" import "time"
type BetOutcome struct { type BetOutcome struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
BetID int64 `json:"bet_id" example:"1"` BetID int64 `json:"bet_id" example:"1"`
EventID int64 `json:"event_id" example:"1"` EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"` OddID int64 `json:"odd_id" example:"1"`
HomeTeamName string `json:"home_team_name" example:"Manchester"` HomeTeamName string `json:"home_team_name" example:"Manchester"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"` AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"` MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"` MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"` Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` OddHeader string `json:"odd_header" example:"1"`
OddHandicap string `json:"odd_handicap" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }
type CreateBetOutcome struct { type CreateBetOutcome struct {
@ -26,6 +29,8 @@ type CreateBetOutcome struct {
MarketName string `json:"market_name" example:"Fulltime Result"` MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"` Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` OddName string `json:"odd_name" example:"1"`
OddHeader string `json:"odd_header" example:"1"`
OddHandicap string `json:"odd_handicap" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }

View File

@ -3,43 +3,45 @@ package domain
import ( import (
"encoding/json" "encoding/json"
"time" "time"
) )
type RawMessage interface{}
type RawMessage interface{}
type Market struct { type Market struct {
EventID string EventID string
FI string FI string
MarketCategory string MarketCategory string
MarketType string MarketType string
MarketName string MarketName string
MarketID string MarketID string
UpdatedAt time.Time UpdatedAt time.Time
Odds []json.RawMessage Odds []json.RawMessage
Name string Name string
Handicap string Handicap string
OddsVal float64 OddsVal float64
} }
type Odd struct { type Odd struct {
EventID string `json:"event_id"` EventID string `json:"event_id"`
Fi string `json:"fi"` Fi string `json:"fi"`
MarketType string `json:"market_type"` MarketType string `json:"market_type"`
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
MarketCategory string `json:"market_category"` MarketCategory string `json:"market_category"`
MarketID string `json:"market_id"` MarketID string `json:"market_id"`
Name string `json:"name"` Name string `json:"name"`
Handicap string `json:"handicap"` Handicap string `json:"handicap"`
OddsValue float64 `json:"odds_value"` OddsValue float64 `json:"odds_value"`
Section string `json:"section"` Section string `json:"section"`
Category string `json:"category"` Category string `json:"category"`
RawOdds []RawMessage `json:"raw_odds"` RawOdds []RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"` FetchedAt time.Time `json:"fetched_at"`
Source string `json:"source"` Source string `json:"source"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }
type RawOddsByMarketID struct { type RawOddsByMarketID struct {
ID int64 `json:"id"` ID int64 `json:"id"`
RawOdds []RawMessage `json:"raw_odds"` MarketName string `json:"market_name"`
FetchedAt time.Time `json:"fetched_at"` Handicap string `json:"handicap"`
} RawOdds []RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"`
}

View File

@ -6,13 +6,15 @@ type TicketOutcome struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
TicketID int64 `json:"ticket_id" example:"1"` TicketID int64 `json:"ticket_id" example:"1"`
EventID int64 `json:"event_id" example:"1"` EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"`
HomeTeamName string `json:"home_team_name" example:"Manchester"` HomeTeamName string `json:"home_team_name" example:"Manchester"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"` AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"` MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"` MarketName string `json:"market_name" example:"Fulltime Result"`
OddID int64 `json:"odd_id" example:"1"`
Odd float32 `json:"odd" example:"1.5"` Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` OddName string `json:"odd_name" example:"1"`
OddHeader string `json:"odd_header" example:"1"`
OddHandicap string `json:"odd_handicap" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }
@ -26,6 +28,8 @@ type CreateTicketOutcome struct {
MarketName string `json:"market_name" example:"Fulltime Result"` MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"` Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` OddName string `json:"odd_name" example:"1"`
OddHeader string `json:"odd_header" example:"1"`
OddHandicap string `json:"odd_handicap" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }

View File

@ -46,6 +46,8 @@ func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
MarketName: outcome.MarketName, MarketName: outcome.MarketName,
Odd: outcome.Odd, Odd: outcome.Odd,
OddName: outcome.OddName, OddName: outcome.OddName,
OddHeader: outcome.OddHeader,
OddHandicap: outcome.OddHandicap,
Expires: outcome.Expires.Time, Expires: outcome.Expires.Time,
}) })
} }
@ -82,6 +84,8 @@ func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateB
MarketName: betOutcome.MarketName, MarketName: betOutcome.MarketName,
Odd: betOutcome.Odd, Odd: betOutcome.Odd,
OddName: betOutcome.OddName, OddName: betOutcome.OddName,
OddHeader: betOutcome.OddHeader,
OddHandicap: betOutcome.OddHandicap,
Expires: pgtype.Timestamp{ Expires: pgtype.Timestamp{
Time: betOutcome.Expires, Time: betOutcome.Expires,
Valid: true, Valid: true,
@ -193,6 +197,14 @@ func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) err
return err return err
} }
func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error {
err := s.queries.UpdateStatus(ctx, dbgen.UpdateStatusParams{
ID: id,
Status: int32(status),
})
return err
}
func (s *Store) DeleteBet(ctx context.Context, id int64) error { func (s *Store) DeleteBet(ctx context.Context, id int64) error {
return s.queries.DeleteBet(ctx, id) return s.queries.DeleteBet(ctx, id)
} }

View File

@ -6,6 +6,7 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
@ -86,6 +87,42 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
} }
return upcomingEvents, nil return upcomingEvents, nil
} }
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error) {
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
Limit: limit,
Offset: offset * limit,
})
if err != nil {
return nil, 0, err
}
upcomingEvents := make([]domain.UpcomingEvent, len(events))
for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID,
SportID: e.SportID.String,
MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String,
AwayTeamID: e.AwayTeamID.String,
HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String,
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
}
}
totalCount, err := s.queries.GetTotalEvents(ctx)
if err != nil {
return nil, 0, err
}
numberOfPages := (totalCount) / int64(limit)
return upcomingEvents, numberOfPages, nil
}
func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
event, err := s.queries.GetUpcomingByID(ctx, ID) event, err := s.queries.GetUpcomingByID(ctx, ID)
if err != nil { if err != nil {
@ -108,4 +145,3 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
StartTime: event.StartTime.Time.UTC(), StartTime: event.StartTime.Time.UTC(),
}, nil }, nil
} }

View File

@ -3,7 +3,6 @@ package repository
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"strconv" "strconv"
"time" "time"
@ -180,28 +179,22 @@ func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upco
params := dbgen.GetRawOddsByMarketIDParams{ params := dbgen.GetRawOddsByMarketIDParams{
MarketID: pgtype.Text{String: rawOddsID, Valid: true}, MarketID: pgtype.Text{String: rawOddsID, Valid: true},
Fi: pgtype.Text{String: upcomingID, Valid: true}, Fi: pgtype.Text{String: upcomingID, Valid: true},
Limit: 1,
Offset: 0,
} }
rows, err := s.queries.GetRawOddsByMarketID(ctx, params) odds, err := s.queries.GetRawOddsByMarketID(ctx, params)
if err != nil { if err != nil {
return domain.RawOddsByMarketID{}, err return domain.RawOddsByMarketID{}, err
} }
if len(rows) == 0 {
return domain.RawOddsByMarketID{}, fmt.Errorf("no raw odds found for market_id: %s", rawOddsID)
}
row := rows[0]
var rawOdds []json.RawMessage var rawOdds []json.RawMessage
if err := json.Unmarshal(row.RawOdds, &rawOdds); err != nil { if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil {
return domain.RawOddsByMarketID{}, err return domain.RawOddsByMarketID{}, err
} }
return domain.RawOddsByMarketID{ return domain.RawOddsByMarketID{
ID: int64(row.ID), ID: int64(odds.ID),
MarketName: odds.MarketName.String,
Handicap: odds.Handicap.String,
RawOdds: func() []domain.RawMessage { RawOdds: func() []domain.RawMessage {
converted := make([]domain.RawMessage, len(rawOdds)) converted := make([]domain.RawMessage, len(rawOdds))
for i, r := range rawOdds { for i, r := range rawOdds {
@ -209,7 +202,7 @@ func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upco
} }
return converted return converted
}(), }(),
FetchedAt: row.FetchedAt.Time, FetchedAt: odds.FetchedAt.Time,
}, nil }, nil
} }

View File

@ -32,6 +32,8 @@ func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket {
MarketName: outcome.MarketName, MarketName: outcome.MarketName,
Odd: outcome.Odd, Odd: outcome.Odd,
OddName: outcome.OddName, OddName: outcome.OddName,
OddHeader: outcome.OddHeader,
OddHandicap: outcome.OddHandicap,
Expires: outcome.Expires.Time, Expires: outcome.Expires.Time,
}) })
} }
@ -54,6 +56,8 @@ func convertDBCreateTicketOutcome(ticketOutcome domain.CreateTicketOutcome) dbge
MarketName: ticketOutcome.MarketName, MarketName: ticketOutcome.MarketName,
Odd: ticketOutcome.Odd, Odd: ticketOutcome.Odd,
OddName: ticketOutcome.OddName, OddName: ticketOutcome.OddName,
OddHeader: ticketOutcome.OddHeader,
OddHandicap: ticketOutcome.OddHandicap,
Expires: pgtype.Timestamp{ Expires: pgtype.Timestamp{
Time: ticketOutcome.Expires, Time: ticketOutcome.Expires,
Valid: true, Valid: true,

View File

@ -14,5 +14,6 @@ type BetStore interface {
GetAllBets(ctx context.Context) ([]domain.GetBet, error) GetAllBets(ctx context.Context) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error
DeleteBet(ctx context.Context, id int64) error DeleteBet(ctx context.Context, id int64) error
} }

View File

@ -42,6 +42,10 @@ func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) e
return s.betStore.UpdateCashOut(ctx, id, cashedOut) return s.betStore.UpdateCashOut(ctx, id, cashedOut)
} }
func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error {
return s.betStore.UpdateStatus(ctx, id, status)
}
func (s *Service) DeleteBet(ctx context.Context, id int64) error { func (s *Service) DeleteBet(ctx context.Context, id int64) error {
return s.betStore.DeleteBet(ctx, id) return s.betStore.DeleteBet(ctx, id)
} }

View File

@ -10,5 +10,6 @@ type Service interface {
FetchLiveEvents(ctx context.Context) error FetchLiveEvents(ctx context.Context) error
FetchUpcomingEvents(ctx context.Context) error FetchUpcomingEvents(ctx context.Context) error
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error)
GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
} }

View File

@ -110,10 +110,10 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
var data struct { var data struct {
Success int `json:"success"` Success int `json:"success"`
Results []struct { Results []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"`
League struct { League struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} `json:"league"` } `json:"league"`
@ -178,6 +178,10 @@ func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEv
return s.store.GetAllUpcomingEvents(ctx) return s.store.GetAllUpcomingEvents(ctx)
} }
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error) {
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset)
}
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
return s.store.GetUpcomingEventByID(ctx, ID) return s.store.GetUpcomingEventByID(ctx, ID)
} }

View File

@ -10,5 +10,5 @@ type Service interface {
FetchNonLiveOdds(ctx context.Context) error FetchNonLiveOdds(ctx context.Context) error
GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error)
GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error)
GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) ([]domain.RawOddsByMarketID, error) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error)
} }

View File

@ -119,13 +119,13 @@ func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, err
return s.store.GetALLPrematchOdds(ctx) return s.store.GetALLPrematchOdds(ctx)
} }
func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) ([]domain.RawOddsByMarketID, error) { func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) {
rows, err := s.store.GetRawOddsByMarketID(ctx, marketID, upcomingID) rows, err := s.store.GetRawOddsByMarketID(ctx, marketID, upcomingID)
if err != nil { if err != nil {
return nil, err return domain.RawOddsByMarketID{}, err
} }
return []domain.RawOddsByMarketID{rows}, nil return rows, nil
} }
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) { func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {

View File

@ -1,8 +1,7 @@
package httpserver package httpserver
import ( import (
// "context" "fmt"
"log" "log"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
@ -19,7 +18,6 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
}{ }{
// { // {
// spec: "0 0 * * * *", // Every hour // spec: "0 0 * * * *", // Every hour
// task: func() { // task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
@ -48,6 +46,8 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
job.task()
fmt.Printf("here at")
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err) log.Fatalf("Failed to schedule cron job: %v", err)
} }

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"log/slog" "log/slog"
"strconv" "strconv"
"time" "time"
@ -9,6 +10,8 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -18,16 +21,16 @@ import (
) )
type CreateBetOutcomeReq struct { type CreateBetOutcomeReq struct {
BetID int64 `json:"bet_id" example:"1"` // BetID int64 `json:"bet_id" example:"1"`
EventID int64 `json:"event_id" example:"1"` EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"` OddID int64 `json:"odd_id" example:"1"`
HomeTeamName string `json:"home_team_name" example:"Manchester"` MarketID int64 `json:"market_id" example:"1"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"` // HomeTeamName string `json:"home_team_name" example:"Manchester"`
MarketID int64 `json:"market_id" example:"1"` // AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketName string `json:"market_name" example:"Fulltime Result"` // MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"` // Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` // OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` // Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }
type NullableInt64 struct { type NullableInt64 struct {
@ -139,7 +142,7 @@ func convertBet(bet domain.GetBet) BetRes {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /bet [post] // @Router /bet [post]
func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, branchSvc *branch.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, branchSvc *branch.Service, walletSvc *wallet.Service, eventSvc event.Service, oddSvc odds.ServiceImpl, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
// Get user_id from middleware // Get user_id from middleware
@ -160,6 +163,8 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service,
return nil return nil
} }
// Validating user by role
// Differentiating between offline and online bets
user, err := userSvc.GetUserByID(c.Context(), userID) user, err := userSvc.GetUserByID(c.Context(), userID)
cashoutUUID := uuid.New() cashoutUUID := uuid.New()
var bet domain.Bet var bet domain.Bet
@ -226,28 +231,88 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service,
}) })
} }
// TODO Validate Outcomes Here and make sure they didn't expire
if err != nil { if err != nil {
logger.Error("CreateBetReq failed", "error", err) logger.Error("CreateBetReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
} }
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
//
// TODO Validate Outcomes Here and make sure they didn't expire
// Validation for creating tickets
if len(req.Outcomes) > 30 {
response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
return nil
}
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
for _, outcome := range req.Outcomes { for _, outcome := range req.Outcomes {
eventIDStr := strconv.FormatInt(outcome.EventID, 10)
marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
oddIDStr := strconv.FormatInt(outcome.OddID, 10)
event, err := eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
return nil
}
// Checking to make sure the event hasn't already started
currentTime := time.Now()
if event.StartTime.Before(currentTime) {
response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
return nil
}
odds, err := oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
return nil
}
type rawOddType struct {
ID string
Name string
Odds string
Header string
Handicap string
}
var selectedOdd rawOddType
var isOddFound bool = false
for _, raw := range odds.RawOdds {
var rawOdd rawOddType
rawBytes, err := json.Marshal(raw)
err = json.Unmarshal(rawBytes, &rawOdd)
if err != nil {
fmt.Println("Failed to unmarshal raw odd:", err)
continue
}
if rawOdd.ID == oddIDStr {
selectedOdd = rawOdd
isOddFound = true
}
}
if !isOddFound {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
return nil
}
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
outcomes = append(outcomes, domain.CreateBetOutcome{ outcomes = append(outcomes, domain.CreateBetOutcome{
BetID: bet.ID, BetID: bet.ID,
EventID: outcome.EventID, EventID: outcome.EventID,
OddID: outcome.OddID, OddID: outcome.OddID,
HomeTeamName: outcome.HomeTeamName,
AwayTeamName: outcome.AwayTeamName,
MarketID: outcome.MarketID, MarketID: outcome.MarketID,
MarketName: outcome.MarketName, HomeTeamName: event.HomeTeam,
Odd: outcome.Odd, AwayTeamName: event.AwayTeam,
OddName: outcome.OddName, MarketName: odds.MarketName,
Expires: outcome.Expires, Odd: float32(parsedOdd),
OddName: selectedOdd.Name,
OddHeader: selectedOdd.Header,
OddHandicap: selectedOdd.Handicap,
Expires: event.StartTime,
}) })
} }
rows, err := betSvc.CreateBetOutcome(c.Context(), outcomes) rows, err := betSvc.CreateBetOutcome(c.Context(), outcomes)
if err != nil { if err != nil {

View File

@ -96,17 +96,22 @@ func GetRawOddsByMarketID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fi
// @Tags prematch // @Tags prematch
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {array} domain.UpcomingEvent // @Success 200 {array} domain.UpcomingEvent
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/events [get] // @Router /prematch/events [get]
func GetAllUpcomingEvents(logger *slog.Logger, eventSvc event.Service) fiber.Handler { func GetAllUpcomingEvents(logger *slog.Logger, eventSvc event.Service) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
events, err := eventSvc.GetAllUpcomingEvents(c.Context()) page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
events, total, err := eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page) - 1)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil)
} }
return response.WriteJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil) return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil, page, int(total))
} }
} }

View File

@ -1,11 +1,15 @@
package handlers package handlers
import ( import (
"encoding/json"
"fmt"
"log/slog" "log/slog"
"strconv" "strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
@ -13,16 +17,16 @@ import (
) )
type CreateTicketOutcomeReq struct { type CreateTicketOutcomeReq struct {
TicketID int64 `json:"ticket_id" example:"1"` // TicketID int64 `json:"ticket_id" example:"1"`
EventID int64 `json:"event_id" example:"1"` EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"` OddID int64 `json:"odd_id" example:"1"`
HomeTeamName string `json:"home_team_name" example:"Manchester"` MarketID int64 `json:"market_id" example:"1"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"` // HomeTeamName string `json:"home_team_name" example:"Manchester"`
MarketID int64 `json:"market_id" example:"1"` // AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketName string `json:"market_name" example:"Fulltime Result"` // MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"` // Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"` // OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` // Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
} }
type CreateTicketReq struct { type CreateTicketReq struct {
@ -46,8 +50,7 @@ type CreateTicketRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /ticket [post] // @Router /ticket [post]
func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, eventSvc event.Service, oddSvc odds.ServiceImpl, validator *customvalidator.CustomValidator) fiber.Handler {
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
var req CreateTicketReq var req CreateTicketReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -64,6 +67,79 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service,
} }
// TODO Validate Outcomes Here and make sure they didn't expire // TODO Validate Outcomes Here and make sure they didn't expire
// Validation for creating tickets
if len(req.Outcomes) > 30 {
response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
return nil
}
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
for _, outcome := range req.Outcomes {
eventIDStr := strconv.FormatInt(outcome.EventID, 10)
marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
oddIDStr := strconv.FormatInt(outcome.OddID, 10)
event, err := eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
return nil
}
// Checking to make sure the event hasn't already started
currentTime := time.Now()
if event.StartTime.Before(currentTime) {
response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
return nil
}
odds, err := oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
return nil
}
type rawOddType struct {
ID string
Name string
Odds string
Header string
Handicap string
}
var selectedOdd rawOddType
var isOddFound bool = false
for _, raw := range odds.RawOdds {
var rawOdd rawOddType
rawBytes, err := json.Marshal(raw)
err = json.Unmarshal(rawBytes, &rawOdd)
if err != nil {
fmt.Println("Failed to unmarshal raw odd:", err)
continue
}
if rawOdd.ID == oddIDStr {
selectedOdd = rawOdd
isOddFound = true
}
}
if !isOddFound {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
return nil
}
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
outcomes = append(outcomes, domain.CreateTicketOutcome{
EventID: outcome.EventID,
OddID: outcome.OddID,
MarketID: outcome.MarketID,
HomeTeamName: event.HomeTeam,
AwayTeamName: event.AwayTeam,
MarketName: odds.MarketName,
Odd: float32(parsedOdd),
OddName: selectedOdd.Name,
OddHeader: selectedOdd.Header,
OddHandicap: selectedOdd.Handicap,
Expires: event.StartTime,
})
}
ticket, err := ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ ticket, err := ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
@ -76,22 +152,11 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service,
}) })
} }
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) // Add the ticket id now that it has fetched from the database
for index := range outcomes {
for _, outcome := range req.Outcomes { outcomes[index].TicketID = ticket.ID
outcomes = append(outcomes, domain.CreateTicketOutcome{
TicketID: ticket.ID,
EventID: outcome.EventID,
OddID: outcome.OddID,
HomeTeamName: outcome.HomeTeamName,
AwayTeamName: outcome.AwayTeamName,
MarketID: outcome.MarketID,
MarketName: outcome.MarketName,
Odd: outcome.Odd,
OddName: outcome.OddName,
Expires: outcome.Expires,
})
} }
rows, err := ticketSvc.CreateTicketOutcome(c.Context(), outcomes) rows, err := ticketSvc.CreateTicketOutcome(c.Context(), outcomes)
if err != nil { if err != nil {

View File

@ -18,12 +18,15 @@ type APIResponse struct {
Message string `json:"message"` Message string `json:"message"`
Data interface{} `json:"data,omitempty"` Data interface{} `json:"data,omitempty"`
Metadata interface{} `json:"metadata,omitempty"` Metadata interface{} `json:"metadata,omitempty"`
Page *int `json:"page,omitempty"`
Total *int `json:"total,omitempty"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
} }
func NewAPIResponse( func NewAPIResponse(
status Status, message string, status Status, message string,
data interface{}, metadata interface{}, data interface{}, metadata interface{},
page *int, total *int,
) APIResponse { ) APIResponse {
return APIResponse{ return APIResponse{
@ -32,6 +35,8 @@ func NewAPIResponse(
Data: data, Data: data,
Metadata: metadata, Metadata: metadata,
Timestamp: time.Now(), Timestamp: time.Now(),
Page: page,
Total: total,
} }
} }
func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}) error { func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}) error {
@ -41,7 +46,18 @@ func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interfac
} else { } else {
apiStatus = Error apiStatus = Error
} }
apiRes := NewAPIResponse(apiStatus, message, data, metadata) apiRes := NewAPIResponse(apiStatus, message, data, metadata, nil, nil)
return c.Status(status).JSON(apiRes)
}
func WritePaginatedJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}, page int, total int) error {
var apiStatus Status
if status >= 200 && status <= 299 {
apiStatus = Success
} else {
apiStatus = Error
}
apiRes := NewAPIResponse(apiStatus, message, data, metadata, &page, &total)
return c.Status(status).JSON(apiRes) return c.Status(status).JSON(apiRes)
} }

View File

@ -86,12 +86,12 @@ func (a *App) initAppRoutes() {
a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, handlers.DeleteBranchOperation(a.logger, a.branchSvc, a.validator)) a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, handlers.DeleteBranchOperation(a.logger, a.branchSvc, a.validator))
// Ticket // Ticket
a.fiber.Post("/ticket", handlers.CreateTicket(a.logger, a.ticketSvc, a.validator)) a.fiber.Post("/ticket", handlers.CreateTicket(a.logger, a.ticketSvc, a.eventSvc, *a.prematchSvc, a.validator))
a.fiber.Get("/ticket", handlers.GetAllTickets(a.logger, a.ticketSvc, a.validator)) a.fiber.Get("/ticket", handlers.GetAllTickets(a.logger, a.ticketSvc, a.validator))
a.fiber.Get("/ticket/:id", handlers.GetTicketByID(a.logger, a.ticketSvc, a.validator)) a.fiber.Get("/ticket/:id", handlers.GetTicketByID(a.logger, a.ticketSvc, a.validator))
// Bet // Bet
a.fiber.Post("/bet", a.authMiddleware, handlers.CreateBet(a.logger, a.betSvc, a.userSvc, a.branchSvc, a.walletSvc, a.validator)) a.fiber.Post("/bet", a.authMiddleware, handlers.CreateBet(a.logger, a.betSvc, a.userSvc, a.branchSvc, a.walletSvc, a.eventSvc, *a.prematchSvc, a.validator))
a.fiber.Get("/bet", a.authMiddleware, handlers.GetAllBet(a.logger, a.betSvc, a.validator)) a.fiber.Get("/bet", a.authMiddleware, handlers.GetAllBet(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet/:id", a.authMiddleware, handlers.GetBetByID(a.logger, a.betSvc, a.validator)) a.fiber.Get("/bet/:id", a.authMiddleware, handlers.GetBetByID(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, handlers.GetBetByCashoutID(a.logger, a.betSvc, a.validator)) a.fiber.Get("/bet/cashout/:id", a.authMiddleware, handlers.GetBetByCashoutID(a.logger, a.betSvc, a.validator))