From b4a21a5ddb834ddd536b9cdb7cfbba2046c0feb3 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 14 Apr 2025 07:59:23 +0300 Subject: [PATCH] - --- db/migrations/000001_fortune.up.sql | 27 +- db/query/bet.sql | 19 +- db/query/ticket.sql | 15 +- docs/docs.go | 254 +++++++++++++++--- docs/swagger.json | 254 +++++++++++++++--- docs/swagger.yaml | 181 +++++++++++-- gen/db/bet.sql.go | 58 ++-- gen/db/copyfrom.go | 18 +- gen/db/models.go | 35 ++- gen/db/odds.sql.go | 107 ++++---- gen/db/ticket.sql.go | 26 +- internal/domain/bet.go | 30 ++- internal/domain/ticket.go | 30 ++- internal/repository/bet.go | 54 +++- internal/repository/ticket.go | 51 ++-- internal/services/bet/port.go | 1 + internal/services/bet/service.go | 3 + internal/web_server/app.go | 8 +- internal/web_server/cron.go | 93 +++---- internal/web_server/handlers/bet_handler.go | 89 ++++-- .../web_server/handlers/ticket_handler.go | 34 ++- .../handlers/transaction_handler.go | 80 ++++-- internal/web_server/routes.go | 5 +- 23 files changed, 1100 insertions(+), 372 deletions(-) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 610b920..ef85495 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -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.*, diff --git a/db/query/bet.sql b/db/query/bet.sql index 01230b2..bc37717 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -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 diff --git a/db/query/ticket.sql b/db/query/ticket.sql index debcb48..86d82f5 100644 --- a/db/query/ticket.sql +++ b/db/query/ticket.sql @@ -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; diff --git a/docs/docs.go b/docs/docs.go index 67e9ac0..4e4fd35 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { diff --git a/docs/swagger.json b/docs/swagger.json index fc559e3..e20677f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ad5e9cd..306272a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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: diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index c429b91..d8914d7 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -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 diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go index f3dffed..5428a01 100644 --- a/gen/db/copyfrom.go +++ b/gen/db/copyfrom.go @@ -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}) } diff --git a/gen/db/models.go b/gen/db/models.go index 9a9993c..8d02167 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -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"` diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 5f966bf..f7e88ab 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -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, - raw_odds, +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 { diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index 59ebd69..d49ca8c 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -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 } diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 23b3ee8..300b65e 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -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 diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index 50e23f3..6cdf400 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -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 diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 5467d0f..23b81d1 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -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 } diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index cefdbf8..50eff64 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -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 diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 2c7c133..3b10393 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -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) diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 83cbb27..b5f61ef 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -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) } diff --git a/internal/web_server/app.go b/internal/web_server/app.go index c81a930..b41dcf0 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -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{ diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index ddf9677..461c0cb 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -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) + // } + // }, + // }, - - for _, job := range schedule { - if _, err := c.AddFunc(job.spec, job.task); err != nil { - log.Fatalf("Failed to schedule cron job: %v", err) - } - } + // { + // spec: "*/5 * * * * *", // Every 5 seconds + // task: func() { + // if err := eventService.FetchLiveEvents(context.Background()); err != nil { + // log.Printf("FetchLiveEvents error: %v", err) + // } + // }, + // }, - c.Start() - log.Println("Cron jobs started for event and odds services") -} \ No newline at end of file + // { + // 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) + } + } + + c.Start() + log.Println("Cron jobs started for event and odds services") +} diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index dca3acc..8baae19 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -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 } diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index 013eca6..b9e398b 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -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) diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index c5b5ea4..7cf85e1 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -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) diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index b968e15..5de11e4 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -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))