This commit is contained in:
Samuel Tariku 2025-04-14 07:59:23 +03:00
parent 788119f718
commit b4a21a5ddb
23 changed files with 1100 additions and 372 deletions

View File

@ -56,11 +56,12 @@ CREATE TABLE IF NOT EXISTS bets (
CHECK (
user_id IS NOT NULL
OR branch_id IS NOT NULL
)
),
UNIQUE(cashier_id)
);
CREATE TABLE IF NOT EXISTS tickets (
id BIGSERIAL PRIMARY KEY,
amount BIGINT NULL,
amount BIGINT NOT NULL,
total_odds REAL NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
@ -68,14 +69,28 @@ CREATE TABLE IF NOT EXISTS tickets (
CREATE TABLE IF NOT EXISTS bet_outcomes (
id BIGSERIAL PRIMARY KEY,
bet_id BIGINT NOT NULL,
event_id bigint not null,
odd_id BIGINT NOT NULL
event_id BIGINT NOT null,
odd_id BIGINT NOT NULL,
home_team_name VARCHAR(255) NOT NULL,
away_team_name VARCHAR(255) NOT NULL,
market_id BIGINT NOT NULL,
market_name VARCHAR(255) NOT NULL,
odd REAL NOT NULL,
odd_name VARCHAR(255) NOT NULL,
expires TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS ticket_outcomes (
id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL,
event_id bigint not null,
odd_id BIGINT NOT NULL
event_id BIGINT NOT null,
odd_id BIGINT NOT NULL,
home_team_name VARCHAR(255) NOT NULL,
away_team_name VARCHAR(255) NOT NULL,
market_id BIGINT NOT NULL,
market_name VARCHAR(255) NOT NULL,
odd REAL NOT NULL,
odd_name VARCHAR(255) NOT NULL,
expires TIMESTAMP NOT NULL
);
CREATE VIEW bet_with_outcomes AS
SELECT bets.*,

View File

@ -13,8 +13,19 @@ INSERT INTO bets (
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *;
-- name: CreateBetOutcome :copyfrom
INSERT INTO bet_outcomes (bet_id, event_id, odd_id)
VALUES ($1, $2, $3);
INSERT INTO bet_outcomes (
bet_id,
event_id,
odd_id,
home_team_name,
away_team_name,
market_id,
market_name,
odd,
odd_name,
expires
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
-- name: GetAllBets :many
SELECT *
FROM bet_with_outcomes;
@ -22,8 +33,8 @@ FROM bet_with_outcomes;
SELECT *
FROM bet_with_outcomes
WHERE id = $1;
-- name: GetBetByCashoutID :many
SELECT
-- name: GetBetByCashoutID :one
SELECT *
FROM bet_with_outcomes
WHERE cashout_id = $1;
-- name: GetBetByBranchID :many

View File

@ -3,8 +3,19 @@ INSERT INTO tickets (amount, total_odds)
VALUES ($1, $2)
RETURNING *;
-- name: CreateTicketOutcome :copyfrom
INSERT INTO ticket_outcomes (ticket_id, event_id, odd_id)
VALUES ($1, $2, $3);
INSERT INTO ticket_outcomes (
ticket_id,
event_id,
odd_id,
home_team_name,
away_team_name,
market_id,
market_name,
odd,
odd_name,
expires
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
-- name: GetAllTickets :many
SELECT *
FROM ticket_with_outcomes;

View File

@ -262,6 +262,50 @@ const docTemplate = `{
}
}
},
"/bet/cashout/{id}": {
"get": {
"description": "Gets a single bet by cashout id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"bet"
],
"summary": "Gets bet by cashout id",
"parameters": [
{
"type": "string",
"description": "cashout ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.BetRes"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/bet/{id}": {
"get": {
"description": "Gets a single bet by id",
@ -2577,17 +2621,49 @@ const docTemplate = `{
"domain.BetOutcome": {
"type": "object",
"properties": {
"betID": {
"type": "integer"
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"eventID": {
"type": "integer"
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"id": {
"type": "integer"
"type": "integer",
"example": 1
},
"oddID": {
"type": "integer"
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
}
}
},
@ -2710,18 +2786,46 @@ const docTemplate = `{
"domain.TicketOutcome": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"id": {
"type": "integer",
"example": 1
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
@ -2785,19 +2889,6 @@ const docTemplate = `{
}
}
},
"handlers.BetOutcome": {
"type": "object",
"properties": {
"event_id": {
"type": "integer",
"example": 1
},
"odd_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.BetRes": {
"type": "object",
"properties": {
@ -2809,6 +2900,14 @@ const docTemplate = `{
"type": "integer",
"example": 2
},
"cashed_id": {
"type": "string",
"example": "21234"
},
"cashed_out": {
"type": "boolean",
"example": false
},
"full_name": {
"type": "string",
"example": "John"
@ -2960,6 +3059,51 @@ const docTemplate = `{
}
}
},
"handlers.CreateBetOutcomeReq": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
}
}
},
"handlers.CreateBetReq": {
"type": "object",
"properties": {
@ -2978,7 +3122,7 @@ const docTemplate = `{
"outcomes": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.BetOutcome"
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
}
},
"phone_number": {
@ -3110,6 +3254,51 @@ const docTemplate = `{
}
}
},
"handlers.CreateTicketOutcomeReq": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.CreateTicketReq": {
"type": "object",
"properties": {
@ -3120,7 +3309,7 @@ const docTemplate = `{
"outcomes": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.TicketOutcome"
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
}
},
"total_odds": {
@ -3166,14 +3355,6 @@ const docTemplate = `{
"type": "integer",
"example": 1
},
"branch_id": {
"type": "integer",
"example": 1
},
"cashier_id": {
"type": "integer",
"example": 1
},
"full_name": {
"type": "string",
"example": "John Smith"
@ -3356,19 +3537,6 @@ const docTemplate = `{
}
}
},
"handlers.TicketOutcome": {
"type": "object",
"properties": {
"event_id": {
"type": "integer",
"example": 1
},
"odd_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.TicketRes": {
"type": "object",
"properties": {

View File

@ -254,6 +254,50 @@
}
}
},
"/bet/cashout/{id}": {
"get": {
"description": "Gets a single bet by cashout id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"bet"
],
"summary": "Gets bet by cashout id",
"parameters": [
{
"type": "string",
"description": "cashout ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.BetRes"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/bet/{id}": {
"get": {
"description": "Gets a single bet by id",
@ -2569,17 +2613,49 @@
"domain.BetOutcome": {
"type": "object",
"properties": {
"betID": {
"type": "integer"
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"eventID": {
"type": "integer"
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"id": {
"type": "integer"
"type": "integer",
"example": 1
},
"oddID": {
"type": "integer"
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
}
}
},
@ -2702,18 +2778,46 @@
"domain.TicketOutcome": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"id": {
"type": "integer",
"example": 1
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
@ -2777,19 +2881,6 @@
}
}
},
"handlers.BetOutcome": {
"type": "object",
"properties": {
"event_id": {
"type": "integer",
"example": 1
},
"odd_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.BetRes": {
"type": "object",
"properties": {
@ -2801,6 +2892,14 @@
"type": "integer",
"example": 2
},
"cashed_id": {
"type": "string",
"example": "21234"
},
"cashed_out": {
"type": "boolean",
"example": false
},
"full_name": {
"type": "string",
"example": "John"
@ -2952,6 +3051,51 @@
}
}
},
"handlers.CreateBetOutcomeReq": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"bet_id": {
"type": "integer",
"example": 1
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
}
}
},
"handlers.CreateBetReq": {
"type": "object",
"properties": {
@ -2970,7 +3114,7 @@
"outcomes": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.BetOutcome"
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
}
},
"phone_number": {
@ -3102,6 +3246,51 @@
}
}
},
"handlers.CreateTicketOutcomeReq": {
"type": "object",
"properties": {
"away_team_name": {
"type": "string",
"example": "Liverpool"
},
"event_id": {
"type": "integer",
"example": 1
},
"expires": {
"type": "string",
"example": "2025-04-08T12:00:00Z"
},
"home_team_name": {
"type": "string",
"example": "Manchester"
},
"market_id": {
"type": "integer",
"example": 1
},
"market_name": {
"type": "string",
"example": "Fulltime Result"
},
"odd": {
"type": "number",
"example": 1.5
},
"odd_id": {
"type": "integer",
"example": 1
},
"odd_name": {
"type": "string",
"example": "1"
},
"ticket_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.CreateTicketReq": {
"type": "object",
"properties": {
@ -3112,7 +3301,7 @@
"outcomes": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.TicketOutcome"
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
}
},
"total_odds": {
@ -3158,14 +3347,6 @@
"type": "integer",
"example": 1
},
"branch_id": {
"type": "integer",
"example": 1
},
"cashier_id": {
"type": "integer",
"example": 1
},
"full_name": {
"type": "string",
"example": "John Smith"
@ -3348,19 +3529,6 @@
}
}
},
"handlers.TicketOutcome": {
"type": "object",
"properties": {
"event_id": {
"type": "integer",
"example": 1
},
"odd_id": {
"type": "integer",
"example": 1
}
}
},
"handlers.TicketRes": {
"type": "object",
"properties": {

View File

@ -1,14 +1,39 @@
definitions:
domain.BetOutcome:
properties:
betID:
away_team_name:
example: Liverpool
type: string
bet_id:
example: 1
type: integer
eventID:
event_id:
example: 1
type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
id:
example: 1
type: integer
oddID:
market_id:
example: 1
type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id:
example: 1
type: integer
odd_name:
example: "1"
type: string
type: object
domain.BetStatus:
enum:
@ -96,15 +121,36 @@ definitions:
- RoleCashier
domain.TicketOutcome:
properties:
away_team_name:
example: Liverpool
type: string
event_id:
example: 1
type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
id:
example: 1
type: integer
market_id:
example: 1
type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id:
example: 1
type: integer
odd_name:
example: "1"
type: string
ticket_id:
example: 1
type: integer
@ -151,15 +197,6 @@ definitions:
description: Converted from "time" field in UNIX format
type: string
type: object
handlers.BetOutcome:
properties:
event_id:
example: 1
type: integer
odd_id:
example: 1
type: integer
type: object
handlers.BetRes:
properties:
amount:
@ -168,6 +205,12 @@ definitions:
branch_id:
example: 2
type: integer
cashed_id:
example: "21234"
type: string
cashed_out:
example: false
type: boolean
full_name:
example: John
type: string
@ -274,6 +317,39 @@ definitions:
phone_number_exist:
type: boolean
type: object
handlers.CreateBetOutcomeReq:
properties:
away_team_name:
example: Liverpool
type: string
bet_id:
example: 1
type: integer
event_id:
example: 1
type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
market_id:
example: 1
type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id:
example: 1
type: integer
odd_name:
example: "1"
type: string
type: object
handlers.CreateBetReq:
properties:
amount:
@ -287,7 +363,7 @@ definitions:
type: boolean
outcomes:
items:
$ref: '#/definitions/handlers.BetOutcome'
$ref: '#/definitions/handlers.CreateBetOutcomeReq'
type: array
phone_number:
example: "1234567890"
@ -379,6 +455,39 @@ definitions:
example: SportsBook
type: string
type: object
handlers.CreateTicketOutcomeReq:
properties:
away_team_name:
example: Liverpool
type: string
event_id:
example: 1
type: integer
expires:
example: "2025-04-08T12:00:00Z"
type: string
home_team_name:
example: Manchester
type: string
market_id:
example: 1
type: integer
market_name:
example: Fulltime Result
type: string
odd:
example: 1.5
type: number
odd_id:
example: 1
type: integer
odd_name:
example: "1"
type: string
ticket_id:
example: 1
type: integer
type: object
handlers.CreateTicketReq:
properties:
amount:
@ -386,7 +495,7 @@ definitions:
type: number
outcomes:
items:
$ref: '#/definitions/handlers.TicketOutcome'
$ref: '#/definitions/handlers.CreateTicketOutcomeReq'
type: array
total_odds:
example: 4.22
@ -418,12 +527,6 @@ definitions:
bet_id:
example: 1
type: integer
branch_id:
example: 1
type: integer
cashier_id:
example: 1
type: integer
full_name:
example: John Smith
type: string
@ -550,15 +653,6 @@ definitions:
example: SportsBook
type: string
type: object
handlers.TicketOutcome:
properties:
event_id:
example: 1
type: integer
odd_id:
example: 1
type: integer
type: object
handlers.TicketRes:
properties:
amount:
@ -1046,6 +1140,35 @@ paths:
summary: Updates the cashed out field
tags:
- bet
/bet/cashout/{id}:
get:
consumes:
- application/json
description: Gets a single bet by cashout id
parameters:
- description: cashout ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handlers.BetRes'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Gets bet by cashout id
tags:
- bet
/branch:
get:
consumes:

View File

@ -71,9 +71,16 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
}
type CreateBetOutcomeParams struct {
BetID int64 `json:"bet_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
BetID int64 `json:"bet_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
HomeTeamName string `json:"home_team_name"`
AwayTeamName string `json:"away_team_name"`
MarketID int64 `json:"market_id"`
MarketName string `json:"market_name"`
Odd float32 `json:"odd"`
OddName string `json:"odd_name"`
Expires pgtype.Timestamp `json:"expires"`
}
const DeleteBet = `-- name: DeleteBet :exec
@ -177,33 +184,32 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
return items, nil
}
const GetBetByCashoutID = `-- name: GetBetByCashoutID :many
SELECT
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
FROM bet_with_outcomes
WHERE cashout_id = $1
`
type GetBetByCashoutIDRow struct {
}
func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) ([]GetBetByCashoutIDRow, error) {
rows, err := q.db.Query(ctx, GetBetByCashoutID, cashoutID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetBetByCashoutIDRow
for rows.Next() {
var i GetBetByCashoutIDRow
if err := rows.Scan(); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetWithOutcome, error) {
row := q.db.QueryRow(ctx, GetBetByCashoutID, cashoutID)
var i BetWithOutcome
err := row.Scan(
&i.ID,
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.Outcomes,
)
return i, err
}
const GetBetByID = `-- name: GetBetByID :one

View File

@ -32,6 +32,13 @@ func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) {
r.rows[0].BetID,
r.rows[0].EventID,
r.rows[0].OddID,
r.rows[0].HomeTeamName,
r.rows[0].AwayTeamName,
r.rows[0].MarketID,
r.rows[0].MarketName,
r.rows[0].Odd,
r.rows[0].OddName,
r.rows[0].Expires,
}, nil
}
@ -40,7 +47,7 @@ func (r iteratorForCreateBetOutcome) Err() 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"}, &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", "expires"}, &iteratorForCreateBetOutcome{rows: arg})
}
// iteratorForCreateTicketOutcome implements pgx.CopyFromSource.
@ -66,6 +73,13 @@ func (r iteratorForCreateTicketOutcome) Values() ([]interface{}, error) {
r.rows[0].TicketID,
r.rows[0].EventID,
r.rows[0].OddID,
r.rows[0].HomeTeamName,
r.rows[0].AwayTeamName,
r.rows[0].MarketID,
r.rows[0].MarketName,
r.rows[0].Odd,
r.rows[0].OddName,
r.rows[0].Expires,
}, nil
}
@ -74,5 +88,5 @@ func (r iteratorForCreateTicketOutcome) Err() 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"}, &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", "expires"}, &iteratorForCreateTicketOutcome{rows: arg})
}

View File

@ -25,10 +25,17 @@ type Bet struct {
}
type BetOutcome struct {
ID int64 `json:"id"`
BetID int64 `json:"bet_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
ID int64 `json:"id"`
BetID int64 `json:"bet_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
HomeTeamName string `json:"home_team_name"`
AwayTeamName string `json:"away_team_name"`
MarketID int64 `json:"market_id"`
MarketName string `json:"market_name"`
Odd float32 `json:"odd"`
OddName string `json:"odd_name"`
Expires pgtype.Timestamp `json:"expires"`
}
type BetWithOutcome struct {
@ -148,7 +155,6 @@ type Odd struct {
MarketCategory pgtype.Text `json:"market_category"`
MarketID pgtype.Text `json:"market_id"`
Name pgtype.Text `json:"name"`
Header pgtype.Text `json:"header"`
Handicap pgtype.Text `json:"handicap"`
OddsValue pgtype.Float8 `json:"odds_value"`
Section string `json:"section"`
@ -188,22 +194,29 @@ type SupportedOperation struct {
type Ticket struct {
ID int64 `json:"id"`
Amount pgtype.Int8 `json:"amount"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type TicketOutcome struct {
ID int64 `json:"id"`
TicketID int64 `json:"ticket_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
ID int64 `json:"id"`
TicketID int64 `json:"ticket_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
HomeTeamName string `json:"home_team_name"`
AwayTeamName string `json:"away_team_name"`
MarketID int64 `json:"market_id"`
MarketName string `json:"market_name"`
Odd float32 `json:"odd"`
OddName string `json:"odd_name"`
Expires pgtype.Timestamp `json:"expires"`
}
type TicketWithOutcome struct {
ID int64 `json:"id"`
Amount pgtype.Int8 `json:"amount"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`

View File

@ -12,7 +12,8 @@ import (
)
const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many
SELECT event_id,
SELECT
event_id,
fi,
market_type,
market_name,
@ -28,8 +29,7 @@ SELECT event_id,
source,
is_active
FROM odds
WHERE is_active = true
AND source = 'b365api'
WHERE is_active = true AND source = 'b365api'
`
type GetALLPrematchOddsRow struct {
@ -87,7 +87,8 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
}
const GetPrematchOdds = `-- name: GetPrematchOdds :many
SELECT event_id,
SELECT
event_id,
fi,
market_type,
market_name,
@ -103,8 +104,7 @@ SELECT event_id,
source,
is_active
FROM odds
WHERE is_active = true
AND source = 'b365api'
WHERE is_active = true AND source = 'b365api'
`
type GetPrematchOddsRow struct {
@ -162,7 +162,8 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, er
}
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
SELECT o.event_id,
SELECT
o.event_id,
o.fi,
o.market_type,
o.market_name,
@ -178,12 +179,12 @@ SELECT o.event_id,
o.source,
o.is_active
FROM odds o
JOIN events e ON o.fi = e.id
JOIN events e ON o.fi = e.id
WHERE e.id = $1
AND e.is_live = false
AND e.status = 'upcoming'
AND o.is_active = true
AND o.source = 'b365api'
AND e.is_live = false
AND e.status = 'upcoming'
AND o.is_active = true
AND o.source = 'b365api'
LIMIT $2 OFFSET $3
`
@ -248,13 +249,15 @@ func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPremat
}
const GetRawOddsByID = `-- name: GetRawOddsByID :one
SELECT id,
SELECT
id,
raw_odds,
fetched_at
FROM odds
WHERE raw_odds @> $1::jsonb
AND is_active = true
AND source = 'b365api'
WHERE
raw_odds @> $1::jsonb AND
is_active = true AND
source = 'b365api'
LIMIT 1
`
@ -273,49 +276,35 @@ func (q *Queries) GetRawOddsByID(ctx context.Context, dollar_1 []byte) (GetRawOd
const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec
INSERT INTO odds (
event_id,
fi,
market_type,
market_name,
market_category,
market_id,
name,
handicap,
odds_value,
section,
category,
raw_odds,
is_active,
source,
fetched_at
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15
) ON CONFLICT (market_id, name, handicap) DO
UPDATE
SET odds_value = EXCLUDED.odds_value,
raw_odds = EXCLUDED.raw_odds,
market_type = EXCLUDED.market_type,
market_name = EXCLUDED.market_name,
event_id,
fi,
market_type,
market_name,
market_category,
market_id,
name,
handicap,
odds_value,
section,
category,
raw_odds,
is_active,
source,
fetched_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7,
$8, $9, $10, $11, $12, $13, $14, $15
)
ON CONFLICT (market_id, name, handicap) DO UPDATE SET
odds_value = EXCLUDED.odds_value,
raw_odds = EXCLUDED.raw_odds,
market_type = EXCLUDED.market_type,
market_name = EXCLUDED.market_name,
market_category = EXCLUDED.market_category,
fetched_at = EXCLUDED.fetched_at,
is_active = EXCLUDED.is_active,
source = EXCLUDED.source,
fi = EXCLUDED.fi
fetched_at = EXCLUDED.fetched_at,
is_active = EXCLUDED.is_active,
source = EXCLUDED.source,
fi = EXCLUDED.fi
`
type InsertNonLiveOddParams struct {

View File

@ -18,8 +18,8 @@ RETURNING id, amount, total_odds, created_at, updated_at
`
type CreateTicketParams struct {
Amount pgtype.Int8 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
}
func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) {
@ -36,9 +36,16 @@ func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Tic
}
type CreateTicketOutcomeParams struct {
TicketID int64 `json:"ticket_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
TicketID int64 `json:"ticket_id"`
EventID int64 `json:"event_id"`
OddID int64 `json:"odd_id"`
HomeTeamName string `json:"home_team_name"`
AwayTeamName string `json:"away_team_name"`
MarketID int64 `json:"market_id"`
MarketName string `json:"market_name"`
Odd float32 `json:"odd"`
OddName string `json:"odd_name"`
Expires pgtype.Timestamp `json:"expires"`
}
const DeleteOldTickets = `-- name: DeleteOldTickets :exec
@ -124,7 +131,7 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcom
}
const GetTicketOutcome = `-- name: GetTicketOutcome :many
SELECT id, ticket_id, event_id, odd_id
SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, expires
FROM ticket_outcomes
WHERE ticket_id = $1
`
@ -143,6 +150,13 @@ func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]Ticke
&i.TicketID,
&i.EventID,
&i.OddID,
&i.HomeTeamName,
&i.AwayTeamName,
&i.MarketID,
&i.MarketName,
&i.Odd,
&i.OddName,
&i.Expires,
); err != nil {
return nil, err
}

View File

@ -1,16 +1,32 @@
package domain
import "time"
type BetOutcome struct {
ID int64
BetID int64
EventID int64
OddID int64
ID int64 `json:"id" example:"1"`
BetID int64 `json:"bet_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
type CreateBetOutcome struct {
BetID int64
EventID int64
OddID int64
BetID int64 `json:"bet_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
type BetStatus int

View File

@ -1,16 +1,32 @@
package domain
import "time"
type TicketOutcome struct {
ID int64 `json:"id" example:"1"`
TicketID int64 `json:"ticket_id" example:"1"`
EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"`
ID int64 `json:"id" example:"1"`
TicketID int64 `json:"ticket_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
type CreateTicketOutcome struct {
TicketID int64
EventID int64
OddID int64
TicketID int64 `json:"ticket_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
// ID will serve as the fast code since this doesn't need to be secure

View File

@ -2,6 +2,7 @@ package repository
import (
"context"
// "fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -31,14 +32,21 @@ func convertDBBet(bet dbgen.Bet) domain.Bet {
}
func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
var outcomes []domain.BetOutcome = make([]domain.BetOutcome, len(bet.Outcomes))
var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes))
for _, outcome := range bet.Outcomes {
outcomes = append(outcomes, domain.BetOutcome{
ID: outcome.ID,
BetID: outcome.BetID,
EventID: outcome.EventID,
OddID: outcome.OddID,
ID: outcome.ID,
BetID: outcome.BetID,
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.Time,
})
}
return domain.GetBet{
@ -63,6 +71,24 @@ func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
}
}
func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams {
return dbgen.CreateBetOutcomeParams{
BetID: betOutcome.BetID,
EventID: betOutcome.EventID,
OddID: betOutcome.OddID,
HomeTeamName: betOutcome.HomeTeamName,
AwayTeamName: betOutcome.AwayTeamName,
MarketID: betOutcome.MarketID,
MarketName: betOutcome.MarketName,
Odd: betOutcome.Odd,
OddName: betOutcome.OddName,
Expires: pgtype.Timestamp{
Time: betOutcome.Expires,
Valid: true,
},
}
}
func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams {
return dbgen.CreateBetParams{
Amount: int64(bet.Amount),
@ -96,11 +122,7 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes))
for _, outcome := range outcomes {
dbParams = append(dbParams, dbgen.CreateBetOutcomeParams{
BetID: outcome.BetID,
EventID: outcome.EventID,
OddID: outcome.OddID,
})
dbParams = append(dbParams, convertDBCreateBetOutcome(outcome))
}
rows, err := s.queries.CreateBetOutcome(ctx, dbParams)
@ -113,6 +135,17 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
bet, err := s.queries.GetBetByID(ctx, id)
if err != nil {
return domain.GetBet{}, err
}
return convertDBBetOutcomes(bet), nil
}
func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
bet, err := s.queries.GetBetByCashoutID(ctx, id)
if err != nil {
return domain.GetBet{}, err
}
@ -122,7 +155,6 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
bets, err := s.queries.GetAllBets(ctx)
if err != nil {
return nil, err
}

View File

@ -2,7 +2,6 @@ package repository
import (
"context"
"fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -12,10 +11,9 @@ import (
func convertDBTicket(ticket dbgen.Ticket) domain.Ticket {
return domain.Ticket{
ID: ticket.ID,
Amount: domain.Currency(ticket.Amount.Int64),
Amount: domain.Currency(ticket.Amount),
TotalOdds: ticket.TotalOdds,
}
}
func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket {
@ -24,25 +22,48 @@ func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket {
for _, outcome := range ticket.Outcomes {
outcomes = append(outcomes, domain.TicketOutcome{
ID: outcome.ID,
TicketID: outcome.TicketID,
EventID: outcome.EventID,
OddID: outcome.OddID,
ID: outcome.ID,
TicketID: outcome.TicketID,
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.Time,
})
}
return domain.GetTicket{
ID: ticket.ID,
Amount: domain.Currency(ticket.Amount.Int64),
Amount: domain.Currency(ticket.Amount),
TotalOdds: ticket.TotalOdds,
Outcomes: outcomes,
}
}
func convertDBCreateTicketOutcome(ticketOutcome domain.CreateTicketOutcome) dbgen.CreateTicketOutcomeParams {
return dbgen.CreateTicketOutcomeParams{
TicketID: ticketOutcome.TicketID,
EventID: ticketOutcome.EventID,
OddID: ticketOutcome.OddID,
HomeTeamName: ticketOutcome.HomeTeamName,
AwayTeamName: ticketOutcome.AwayTeamName,
MarketID: ticketOutcome.MarketID,
MarketName: ticketOutcome.MarketName,
Odd: ticketOutcome.Odd,
OddName: ticketOutcome.OddName,
Expires: pgtype.Timestamp{
Time: ticketOutcome.Expires,
Valid: true,
},
}
}
func convertCreateTicket(ticket domain.CreateTicket) dbgen.CreateTicketParams {
return dbgen.CreateTicketParams{
Amount: pgtype.Int8{
Int64: int64(ticket.Amount),
},
Amount: int64(ticket.Amount),
TotalOdds: ticket.TotalOdds,
}
}
@ -61,11 +82,7 @@ func (s *Store) CreateTicketOutcome(ctx context.Context, outcomes []domain.Creat
var dbParams []dbgen.CreateTicketOutcomeParams = make([]dbgen.CreateTicketOutcomeParams, 0, len(outcomes))
for _, outcome := range outcomes {
dbParams = append(dbParams, dbgen.CreateTicketOutcomeParams{
TicketID: outcome.TicketID,
EventID: outcome.EventID,
OddID: outcome.OddID,
})
dbParams = append(dbParams, convertDBCreateTicketOutcome(outcome))
}
rows, err := s.queries.CreateTicketOutcome(ctx, dbParams)
@ -89,7 +106,6 @@ func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket,
func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) {
tickets, err := s.queries.GetAllTickets(ctx)
fmt.Printf("%v", tickets)
if err != nil {
return nil, err
}
@ -97,7 +113,6 @@ func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) {
var result []domain.GetTicket = make([]domain.GetTicket, 0, len(tickets))
for _, ticket := range tickets {
result = append(result, convertDBTicketOutcomes(ticket))
// fmt.Printf("%v", convertDBTicketOutcomes(ticket))
}
return result, nil

View File

@ -9,6 +9,7 @@ import (
type BetStore interface {
CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error)
CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error)
GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error)
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)

View File

@ -27,6 +27,9 @@ func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.Create
func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
return s.betStore.GetBetByID(ctx, id)
}
func (s *Service) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
return s.betStore.GetBetByCashoutID(ctx, id)
}
func (s *Service) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
return s.betStore.GetAllBets(ctx)
}

View File

@ -64,10 +64,10 @@ func NewApp(
})
app.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:8000", // Specify your frontend's origin
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", // Specify the allowed HTTP methods
AllowHeaders: "Content-Type,Authorization,platform", // Specify the allowed headers
AllowCredentials: true,
AllowOrigins: "*", // Specify your frontend's origin
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", // Specify the allowed HTTP methods
AllowHeaders: "Content-Type,Authorization,platform", // Specify the allowed headers
// AllowCredentials: true,
}))
s := &App{

View File

@ -1,61 +1,56 @@
package httpserver
import (
// "context"
"log"
"log"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/robfig/cron/v3"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/robfig/cron/v3"
)
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service) {
c := cron.New(cron.WithSeconds())
schedule := []struct {
spec string
task func()
}{
// {
// spec: "*/30 * * * * *", // Every 30 seconds
// task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
// log.Printf("FetchUpcomingEvents error: %v", err)
// }
// },
// },
// {
// spec: "*/5 * * * * *", // Every 5 seconds
// task: func() {
// if err := eventService.FetchLiveEvents(context.Background()); err != nil {
// log.Printf("FetchLiveEvents error: %v", err)
// }
// },
// },
c := cron.New(cron.WithSeconds())
schedule := []struct {
spec string
task func()
}{
// {
// spec: "*/30 * * * * *", // Every 30 seconds
// task: func() {
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
// log.Printf("FetchNonLiveOdds error: %v", err)
// }
// },
// },
// spec: "*/30 * * * * *", // Every 30 seconds
// task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
// log.Printf("FetchUpcomingEvents error: %v", err)
// }
// },
// },
// {
// spec: "*/5 * * * * *", // Every 5 seconds
// task: func() {
// if err := eventService.FetchLiveEvents(context.Background()); err != nil {
// log.Printf("FetchLiveEvents error: %v", err)
// }
// },
// },
}
// {
// spec: "*/30 * * * * *", // Every 30 seconds
// task: func() {
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
// log.Printf("FetchNonLiveOdds error: %v", err)
// }
// },
// },
}
for _, job := range schedule {
job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err)
}
}
for _, job := range schedule {
if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err)
}
}
c.Start()
log.Println("Cron jobs started for event and odds services")
c.Start()
log.Println("Cron jobs started for event and odds services")
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"log/slog"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
@ -16,10 +17,19 @@ import (
"github.com/google/uuid"
)
type BetOutcome struct {
EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"`
type CreateBetOutcomeReq struct {
BetID int64 `json:"bet_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
type NullableInt64 struct {
Value int64
Valid bool
@ -49,13 +59,13 @@ func (n NullableInt64) MarshalJSON() ([]byte, error) {
}
type CreateBetReq struct {
Outcomes []BetOutcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status domain.BetStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
Outcomes []CreateBetOutcomeReq `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status domain.BetStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
}
type CreateBetRes struct {
@ -69,6 +79,7 @@ type CreateBetRes struct {
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CreatedNumber int64 `json:"created_number" example:"2"`
CashedID string `json:"cashed_id" example:"21234"`
}
type BetRes struct {
ID int64 `json:"id" example:"1"`
@ -81,6 +92,8 @@ type BetRes struct {
BranchID int64 `json:"branch_id" example:"2"`
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CashedOut bool `json:"cashed_out" example:"false"`
CashedID string `json:"cashed_id" example:"21234"`
}
func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
@ -94,6 +107,7 @@ func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
CreatedNumber: createdNumber,
CashedID: bet.CashoutID,
}
}
@ -107,6 +121,10 @@ func convertBet(bet domain.GetBet) BetRes {
PhoneNumber: bet.PhoneNumber,
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
Outcomes: bet.Outcomes,
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
CashedID: bet.CashoutID,
}
}
@ -145,7 +163,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service,
user, err := userSvc.GetUserByID(c.Context(), userID)
cashoutUUID := uuid.New()
var bet domain.Bet
if user.Role != domain.RoleCashier {
if user.Role == domain.RoleCashier {
// Get the branch from the branch ID
branch, err := branchSvc.GetBranchByCashier(c.Context(), user.ID)
@ -167,6 +185,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service,
"error": "Unable to deduct from branch wallet",
})
}
bet, err = betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds,
@ -217,9 +236,16 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service,
for _, outcome := range req.Outcomes {
outcomes = append(outcomes, domain.CreateBetOutcome{
BetID: bet.ID,
EventID: outcome.EventID,
OddID: outcome.OddID,
BetID: bet.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 := betSvc.CreateBetOutcome(c.Context(), outcomes)
@ -287,7 +313,6 @@ func GetBetByID(logger *slog.Logger, betSvc *bet.Service, validator *customvalid
}
bet, err := betSvc.GetBetByID(c.Context(), id)
if err != nil {
logger.Error("Failed to get bet by ID", "betID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
@ -300,6 +325,40 @@ func GetBetByID(logger *slog.Logger, betSvc *bet.Service, validator *customvalid
}
}
// GetBetByCashoutID godoc
// @Summary Gets bet by cashout id
// @Description Gets a single bet by cashout id
// @Tags bet
// @Accept json
// @Produce json
// @Param id path string true "cashout ID"
// @Success 200 {object} BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet/cashout/{id} [get]
func GetBetByCashoutID(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
cashoutID := c.Params("id")
// id, err := strconv.ParseInt(cashoutID, 10, 64)
// if err != nil {
// logger.Error("Invalid cashout ID", "cashoutID", cashoutID, "error", err)
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil)
// }
bet, err := betSvc.GetBetByCashoutID(c.Context(), cashoutID)
if err != nil {
logger.Error("Failed to get bet by ID", "cashoutID", cashoutID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
}
res := convertBet(bet)
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
}
type UpdateCashOutReq struct {
CashedOut bool
}

View File

@ -3,6 +3,7 @@ package handlers
import (
"log/slog"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
@ -11,15 +12,23 @@ import (
"github.com/gofiber/fiber/v2"
)
type TicketOutcome struct {
EventID int64 `json:"event_id" example:"1"`
OddID int64 `json:"odd_id" example:"1"`
type CreateTicketOutcomeReq struct {
TicketID int64 `json:"ticket_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"`
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
MarketID int64 `json:"market_id" example:"1"`
MarketName string `json:"market_name" example:"Fulltime Result"`
Odd float32 `json:"odd" example:"1.5"`
OddName string `json:"odd_name" example:"1"`
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
type CreateTicketReq struct {
Outcomes []TicketOutcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
}
type CreateTicketRes struct {
FastCode int64 `json:"fast_code" example:"1234"`
@ -71,9 +80,16 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service,
for _, outcome := range req.Outcomes {
outcomes = append(outcomes, domain.CreateTicketOutcome{
TicketID: ticket.ID,
EventID: outcome.EventID,
OddID: outcome.OddID,
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)

View File

@ -6,6 +6,8 @@ import (
"strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -32,20 +34,18 @@ type TransactionRes struct {
}
type CreateTransactionReq struct {
Amount float32 `json:"amount" example:"100.0"`
BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"`
Type int64 `json:"type" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
// Payment Details for bank
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
CashoutID string `json:"cashout_id" example:"191212"`
Amount float32 `json:"amount" example:"100.0"`
BetID int64 `json:"bet_id" example:"1"`
Type int64 `json:"type" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
}
func convertTransaction(transaction domain.Transaction) TransactionRes {
@ -79,8 +79,42 @@ func convertTransaction(transaction domain.Transaction) TransactionRes {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /transaction [post]
func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler {
func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, userSvc *user.Service, branchSvc *branch.Service, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
user, err := userSvc.GetUserByID(c.Context(), userID)
if user.Role == domain.RoleCustomer {
logger.Error("CreateTransactionReq failed")
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "unauthorized access",
})
}
// TODO: Add validation to make sure that the bet hasn't already been cashed out by someone else
var branchID int64
if user.Role == domain.RoleAdmin || user.Role == domain.RoleBranchManager || user.Role == domain.RoleSuperAdmin {
branch, err := branchSvc.GetBranchByID(c.Context(), 1)
if err != nil {
logger.Error("CreateTransactionReq no branches")
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "This user type doesn't have branches",
})
}
branchID = branch.ID
} else {
branch, err := branchSvc.GetBranchByCashier(c.Context(), user.ID)
if err != nil {
logger.Error("CreateTransactionReq failed, branch id invalid")
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Branch ID invalid",
})
}
branchID = branch.ID
}
var req CreateTransactionReq
if err := c.BodyParser(&req); err != nil {
logger.Error("CreateTransactionReq failed", "error", err)
@ -91,14 +125,15 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service,
valErrs, ok := validator.Validate(c, req)
if !ok {
logger.Error("CreateTransactionReq failed v", "error", valErrs)
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
}
transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
BranchID: branchID,
CashierID: userID,
Amount: domain.ToCurrency(req.Amount),
BranchID: req.BranchID,
CashierID: req.CashierID,
BetID: req.BetID,
Type: domain.TransactionType(req.Type),
PaymentOption: domain.PaymentOption(req.PaymentOption),
@ -116,6 +151,13 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service,
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
err = betSvc.UpdateCashOut(c.Context(), req.BetID, true)
if err != nil {
logger.Error("CreateTransactionReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
res := convertTransaction(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Transaction Created", res, nil)
@ -175,8 +217,8 @@ func GetAllTransactions(
// Convert transactions to response format
var res []TransactionRes = make([]TransactionRes, 0, len(transactions))
for i, transaction := range transactions {
res[i] = convertTransaction(transaction)
for _, transaction := range transactions {
res = append(res, convertTransaction(transaction))
}
return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil)

View File

@ -91,9 +91,10 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/ticket/:id", handlers.GetTicketByID(a.logger, a.ticketSvc, a.validator))
// Bet
a.fiber.Post("/bet", 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.validator))
a.fiber.Get("/bet", handlers.GetAllBet(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet/:id", handlers.GetBetByID(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet/cashout/:id", handlers.GetBetByCashoutID(a.logger, a.betSvc, a.validator))
a.fiber.Patch("/bet/:id", handlers.UpdateCashOut(a.logger, a.betSvc, a.validator))
a.fiber.Delete("/bet/:id", handlers.DeleteBet(a.logger, a.betSvc, a.validator))
@ -110,7 +111,7 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, handlers.RefillWallet(a.logger, a.walletSvc, a.validator))
// Transactions
a.fiber.Post("/transaction", a.authMiddleware, handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator))
a.fiber.Post("/transaction", a.authMiddleware, handlers.CreateTransaction(a.logger, a.transactionSvc, a.userSvc, a.branchSvc, a.betSvc, a.validator))
a.fiber.Get("/transaction", a.authMiddleware, handlers.GetAllTransactions(a.logger, a.transactionSvc, a.userSvc, a.validator))
a.fiber.Get("/transaction/:id", a.authMiddleware, handlers.GetTransactionByID(a.logger, a.transactionSvc, a.validator))
a.fiber.Patch("/transaction/:id", a.authMiddleware, handlers.UpdateTransactionVerified(a.logger, a.transactionSvc, a.validator))