From 8c536a6d2f6b5d8b244e70e690f160dbefabd7e3 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Wed, 23 Apr 2025 03:44:17 +0300 Subject: [PATCH] company management --- cmd/main.go | 14 +- db/migrations/000001_fortune.down.sql | 21 +- db/migrations/000001_fortune.up.sql | 8 + db/query/bet.sql | 19 +- db/query/company.sql | 24 ++ db/query/events.sql | 18 +- db/query/ticket.sql | 4 + docs/docs.go | 315 +++++++++++++++++- docs/swagger.json | 315 +++++++++++++++++- docs/swagger.yaml | 213 +++++++++++- gen/db/bet.sql.go | 16 + gen/db/company.sql.go | 122 +++++++ gen/db/events.sql.go | 38 ++- gen/db/models.go | 9 + gen/db/ticket.sql.go | 19 +- internal/domain/bet.go | 52 ++- internal/domain/common.go | 12 +- internal/domain/company.go | 19 +- internal/domain/ticket.go | 27 +- internal/repository/bet.go | 15 +- internal/repository/company.go | 74 ++++ internal/repository/event.go | 26 +- internal/repository/ticket.go | 9 + internal/services/bet/port.go | 3 +- internal/services/bet/service.go | 7 +- internal/services/company/port.go | 15 + internal/services/company/service.go | 35 ++ internal/services/event/port.go | 2 +- internal/services/event/service.go | 4 +- internal/services/ticket/port.go | 1 + internal/services/ticket/service.go | 4 + internal/services/wallet/wallet.go | 2 + internal/web_server/app.go | 4 + internal/web_server/handlers/bet_handler.go | 120 ++----- .../web_server/handlers/branch_handler.go | 30 +- internal/web_server/handlers/cashier.go | 42 +-- .../web_server/handlers/company_handler.go | 229 +++++++++++++ internal/web_server/handlers/manager.go | 38 +-- internal/web_server/handlers/prematch.go | 17 +- .../web_server/handlers/ticket_handler.go | 35 +- .../handlers/transaction_handler.go | 22 +- .../web_server/handlers/transfer_handler.go | 20 +- .../web_server/handlers/wallet_handler.go | 6 +- internal/web_server/middleware.go | 10 +- internal/web_server/routes.go | 8 +- 45 files changed, 1681 insertions(+), 362 deletions(-) create mode 100644 db/query/company.sql create mode 100644 gen/db/company.sql.go create mode 100644 internal/repository/company.go create mode 100644 internal/services/company/port.go create mode 100644 internal/services/company/service.go create mode 100644 internal/web_server/handlers/company_handler.go diff --git a/cmd/main.go b/cmd/main.go index a3d8d1c..dfedef6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,11 +16,12 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server" @@ -67,22 +68,23 @@ func main() { eventSvc := event.New(cfg.Bet365Token, store) oddsSvc := odds.New(cfg.Bet365Token, store) - ticketSvc := ticket.NewService(store) betSvc := bet.NewService(store) walletSvc := wallet.NewService(store, store) transactionSvc := transaction.NewService(store) branchSvc := branch.NewService(store) - + companySvc := company.NewService(store) + notificationRepo := repository.NewNotificationRepository(store) notificationSvc := notificationservice.New(notificationRepo, logger, cfg) - + httpserver.StartDataFetchingCrons(eventSvc, oddsSvc) app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{ JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, - }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, notificationSvc, oddsSvc, eventSvc) + }, userSvc, + ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc) logger.Info("Starting server", "port", cfg.Port) diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index 35659ea..82d488d 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -1,22 +1,18 @@ -- Drop tables that depend on service_type_setting DROP TABLE IF EXISTS service_type_setting; - -- Drop product-related tables and types DROP TABLE IF EXISTS product; DROP TYPE IF EXISTS tier_group; - -- Drop onboarding-related tables and types DROP TABLE IF EXISTS verification_key; DROP TABLE IF EXISTS onboarding_user; DROP TYPE IF EXISTS verification_status; DROP TYPE IF EXISTS onboarding_status; - -- Drop staff-related tables and types DROP TABLE IF EXISTS staff_session; DROP TABLE IF EXISTS user_agent; DROP TABLE IF EXISTS staff; DROP TYPE IF EXISTS password_status; - -- Drop mobile app-related tables and types DROP TABLE IF EXISTS user_devices; DROP TABLE IF EXISTS user_session; @@ -25,17 +21,14 @@ DROP TABLE IF EXISTS users; DROP TYPE IF EXISTS device_type; DROP TYPE IF EXISTS registeration_type; DROP TYPE IF EXISTS customer_group; - -- Drop linked accounts and beneficiary tables and types DROP TABLE IF EXISTS beneficiary; DROP TYPE IF EXISTS fund_destination; - -- Drop maker checker-related tables and types DROP TABLE IF EXISTS workflow; DROP TYPE IF EXISTS approval_status; DROP TYPE IF EXISTS action_type; DROP TYPE IF EXISTS context_type; - -- Drop authorization-related tables and types DROP TRIGGER IF EXISTS enforce_unique_array ON policy; DROP FUNCTION IF EXISTS check_unique_array; @@ -43,11 +36,9 @@ DROP TABLE IF EXISTS policy; DROP TABLE IF EXISTS roles; DROP TYPE IF EXISTS policy_action; DROP TYPE IF EXISTS policy_object; - -- Drop bank-related tables and types DROP TABLE IF EXISTS bank; DROP TABLE IF EXISTS flagged_users; - -- Drop transaction-related tables and types DROP TABLE IF EXISTS transaction_daily; DROP TABLE IF EXISTS system_limits; @@ -57,22 +48,18 @@ DROP TYPE IF EXISTS service_type; DROP TYPE IF EXISTS channel; DROP TYPE IF EXISTS transaction_category; DROP TYPE IF EXISTS registration_type; - -- Drop branches and related tables DROP TABLE IF EXISTS branches; DROP TABLE IF EXISTS cities; DROP TABLE IF EXISTS districts; DROP TABLE IF EXISTS regions; - -- Drop activity logs DROP TABLE IF EXISTS activity; - -- Drop ussd account and related enums DROP TABLE IF EXISTS ussd_account; DROP TYPE IF EXISTS ua_pin_status; DROP TYPE IF EXISTS ua_status; DROP TYPE IF EXISTS ua_registaration_type; - -- Drop FortuneBet DROP TABLE IF EXISTS tickets; DROP TABLE IF EXISTS ticket_outcomes; @@ -83,13 +70,9 @@ DROP TABLE IF EXISTS customer_wallets; DROP TABLE IF EXISTS wallet_transfer; DROP TABLE IF EXISTS transactions; DROP TABLE IF EXISTS branches; +DROP TABLE IF EXISTS companies; DROP TABLE IF EXISTS supported_operations; DROP TABLE IF EXISTS refresh_tokens; DROP TABLE IF EXISTS otps; - - - - DROP TABLE IF EXISTS odds; -DROP TABLE IF EXISTS events; - +DROP TABLE IF EXISTS events; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index e24ba3f..111aa73 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -78,6 +78,7 @@ CREATE TABLE IF NOT EXISTS bet_outcomes ( odd_name VARCHAR(255) NOT NULL, odd_header VARCHAR(255) NOT NULL, odd_handicap VARCHAR(255) NOT NULL, + status INT NOT NULL DEFAULT 0, expires TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS ticket_outcomes ( @@ -93,6 +94,7 @@ CREATE TABLE IF NOT EXISTS ticket_outcomes ( odd_name VARCHAR(255) NOT NULL, odd_header VARCHAR(255) NOT NULL, odd_handicap VARCHAR(255) NOT NULL, + status INT NOT NULL DEFAULT 0, expires TIMESTAMP NOT NULL ); CREATE VIEW bet_with_outcomes AS @@ -238,6 +240,12 @@ CREATE TABLE odds ( UNIQUE (event_id, market_id, name, handicap), UNIQUE (event_id, market_id) ); +CREATE TABLE companies ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + admin_id BIGINT NOT NULL, + wallet_id BIGINT NOT NULL +); ALTER TABLE refresh_tokens ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE bets diff --git a/db/query/bet.sql b/db/query/bet.sql index c4eb124..9ebbb30 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -27,7 +27,20 @@ INSERT INTO bet_outcomes ( odd_handicap, expires ) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12); +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ); -- name: GetAllBets :many SELECT * FROM bet_with_outcomes; @@ -48,6 +61,10 @@ UPDATE bets SET cashed_out = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; +-- name: UpdateBetOutcomeStatus :exec +UPDATE bet_outcomes +SET status = $1 +WHERE id = $2; -- name: UpdateStatus :exec UPDATE bets SET status = $2, diff --git a/db/query/company.sql b/db/query/company.sql new file mode 100644 index 0000000..d82cb7a --- /dev/null +++ b/db/query/company.sql @@ -0,0 +1,24 @@ +-- name: CreateCompany :one +INSERT INTO companies ( + name, + admin_id, + wallet_id + ) +VALUES ($1, $2, $3) +RETURNING *; +-- name: GetAllCompanies :many +SELECT * +FROM companies; +-- name: GetCompanyByID :one +SELECT * +FROM companies +WHERE id = $1; +-- name: UpdateCompany :one +UPDATE companies +SET name = $1, + admin_id = $2 +WHERE id = $3 +RETURNING *; +-- name: DeleteCompany :exec +DELETE FROM companies +WHERE id = $1; \ No newline at end of file diff --git a/db/query/events.sql b/db/query/events.sql index 61dbdbb..ab459ae 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -144,7 +144,15 @@ ORDER BY start_time ASC; SELECT COUNT(*) FROM events WHERE is_live = false - AND status = 'upcoming'; + AND status = 'upcoming' + AND ( + league_id = $1 + OR $1 IS NULL + ) + AND ( + sport_id = $2 + OR $2 IS NULL + ); -- name: GetPaginatedUpcomingEvents :many SELECT id, sport_id, @@ -165,6 +173,14 @@ SELECT id, FROM events WHERE is_live = false AND status = 'upcoming' + AND ( + league_id = $3 + OR $3 IS NULL + ) + AND ( + sport_id = $4 + OR $4 IS NULL + ) ORDER BY start_time ASC LIMIT $1 OFFSET $2; -- name: GetUpcomingByID :one diff --git a/db/query/ticket.sql b/db/query/ticket.sql index d8db732..8e2daaf 100644 --- a/db/query/ticket.sql +++ b/db/query/ticket.sql @@ -42,6 +42,10 @@ WHERE id = $1; SELECT * FROM ticket_outcomes WHERE ticket_id = $1; +-- name: UpdateTicketOutcomeStatus :exec +UPDATE ticket_outcomes +SET status = $1 +WHERE id = $2; -- name: DeleteTicket :exec DELETE FROM tickets WHERE id = $1; diff --git a/docs/docs.go b/docs/docs.go index abbb190..cb1802c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -993,6 +993,225 @@ const docTemplate = `{ } } }, + "/company": { + "get": { + "description": "Gets all companies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets all companies", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.CompanyRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Create a company", + "parameters": [ + { + "description": "Creates company", + "name": "createCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}": { + "get": { + "description": "Gets a single company by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets company by id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Updates a company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Company", + "name": "updateCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Delete the company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/company/{id}/branch": { "get": { "description": "Gets branches by company id", @@ -1315,6 +1534,18 @@ const docTemplate = `{ "description": "Page size", "name": "page_size", "in": "query" + }, + { + "type": "string", + "description": "League ID Filter", + "name": "league_id", + "in": "query" + }, + { + "type": "string", + "description": "Sport ID Filter", + "name": "sport_id", + "in": "query" } ], "responses": { @@ -2696,24 +2927,17 @@ const docTemplate = `{ "odd_name": { "type": "string", "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 } } }, - "domain.BetStatus": { - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3 - ], - "x-enum-varnames": [ - "BET_STATUS_PENDING", - "BET_STATUS_WIN", - "BET_STATUS_LOSS", - "BET_STATUS_ERROR" - ] - }, "domain.Odd": { "type": "object", "properties": { @@ -2765,6 +2989,21 @@ const docTemplate = `{ } } }, + "domain.OutcomeStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "OUTCOME_STATUS_PENDING", + "OUTCOME_STATUS_WIN", + "OUTCOME_STATUS_LOSS", + "OUTCOME_STATUS_ERROR" + ] + }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -2869,6 +3108,14 @@ const docTemplate = `{ "type": "string", "example": "1" }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + }, "ticket_id": { "type": "integer", "example": 1 @@ -2976,7 +3223,7 @@ const docTemplate = `{ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -3102,6 +3349,27 @@ const docTemplate = `{ } } }, + "handlers.CompanyRes": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateBetOutcomeReq": { "type": "object", "properties": { @@ -3148,7 +3416,7 @@ const docTemplate = `{ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -3232,6 +3500,19 @@ const docTemplate = `{ } } }, + "handlers.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, "handlers.CreateManagerReq": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 536186b..ad7007a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -985,6 +985,225 @@ } } }, + "/company": { + "get": { + "description": "Gets all companies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets all companies", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.CompanyRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Create a company", + "parameters": [ + { + "description": "Creates company", + "name": "createCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}": { + "get": { + "description": "Gets a single company by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets company by id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Updates a company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Company", + "name": "updateCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Delete the company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/company/{id}/branch": { "get": { "description": "Gets branches by company id", @@ -1307,6 +1526,18 @@ "description": "Page size", "name": "page_size", "in": "query" + }, + { + "type": "string", + "description": "League ID Filter", + "name": "league_id", + "in": "query" + }, + { + "type": "string", + "description": "Sport ID Filter", + "name": "sport_id", + "in": "query" } ], "responses": { @@ -2688,24 +2919,17 @@ "odd_name": { "type": "string", "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 } } }, - "domain.BetStatus": { - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3 - ], - "x-enum-varnames": [ - "BET_STATUS_PENDING", - "BET_STATUS_WIN", - "BET_STATUS_LOSS", - "BET_STATUS_ERROR" - ] - }, "domain.Odd": { "type": "object", "properties": { @@ -2757,6 +2981,21 @@ } } }, + "domain.OutcomeStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "OUTCOME_STATUS_PENDING", + "OUTCOME_STATUS_WIN", + "OUTCOME_STATUS_LOSS", + "OUTCOME_STATUS_ERROR" + ] + }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -2861,6 +3100,14 @@ "type": "string", "example": "1" }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + }, "ticket_id": { "type": "integer", "example": 1 @@ -2968,7 +3215,7 @@ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -3094,6 +3341,27 @@ } } }, + "handlers.CompanyRes": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateBetOutcomeReq": { "type": "object", "properties": { @@ -3140,7 +3408,7 @@ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -3224,6 +3492,19 @@ } } }, + "handlers.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, "handlers.CreateManagerReq": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7a5d23b..cfa3120 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -40,19 +40,11 @@ definitions: odd_name: example: "1" type: string + status: + allOf: + - $ref: '#/definitions/domain.OutcomeStatus' + example: 1 type: object - domain.BetStatus: - enum: - - 0 - - 1 - - 2 - - 3 - type: integer - x-enum-varnames: - - BET_STATUS_PENDING - - BET_STATUS_WIN - - BET_STATUS_LOSS - - BET_STATUS_ERROR domain.Odd: properties: category: @@ -87,6 +79,18 @@ definitions: source: type: string type: object + domain.OutcomeStatus: + enum: + - 0 + - 1 + - 2 + - 3 + type: integer + x-enum-varnames: + - OUTCOME_STATUS_PENDING + - OUTCOME_STATUS_WIN + - OUTCOME_STATUS_LOSS + - OUTCOME_STATUS_ERROR domain.PaymentOption: enum: - 0 @@ -165,6 +169,10 @@ definitions: odd_name: example: "1" type: string + status: + allOf: + - $ref: '#/definitions/domain.OutcomeStatus' + example: 1 ticket_id: example: 1 type: integer @@ -243,7 +251,7 @@ definitions: type: string status: allOf: - - $ref: '#/definitions/domain.BetStatus' + - $ref: '#/definitions/domain.OutcomeStatus' example: 1 total_odds: example: 4.22 @@ -331,6 +339,21 @@ definitions: phone_number_exist: type: boolean type: object + handlers.CompanyRes: + properties: + admin_id: + example: 1 + type: integer + id: + example: 1 + type: integer + name: + example: CompanyName + type: string + wallet_id: + example: 1 + type: integer + type: object handlers.CreateBetOutcomeReq: properties: event_id: @@ -364,7 +387,7 @@ definitions: type: string status: allOf: - - $ref: '#/definitions/domain.BetStatus' + - $ref: '#/definitions/domain.OutcomeStatus' example: 1 total_odds: example: 4.22 @@ -422,6 +445,15 @@ definitions: example: "1234567890" type: string type: object + handlers.CreateCompanyReq: + properties: + admin_id: + example: 1 + type: integer + name: + example: CompanyName + type: string + type: object handlers.CreateManagerReq: properties: email: @@ -1512,6 +1544,151 @@ paths: summary: Update cashier tags: - cashier + /company: + get: + consumes: + - application/json + description: Gets all companies + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.CompanyRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets all companies + tags: + - company + post: + consumes: + - application/json + description: Creates a company + parameters: + - description: Creates company + in: body + name: createCompany + required: true + schema: + $ref: '#/definitions/handlers.CreateCompanyReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a company + tags: + - company + /company/{id}: + delete: + consumes: + - application/json + description: Delete the company + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the company + tags: + - company + get: + consumes: + - application/json + description: Gets a single company by id + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets company by id + tags: + - company + put: + consumes: + - application/json + description: Updates a company + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + - description: Update Company + in: body + name: updateCompany + required: true + schema: + $ref: '#/definitions/handlers.CreateCompanyReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Updates a company + tags: + - company /company/{id}/branch: get: consumes: @@ -1721,6 +1898,14 @@ paths: in: query name: page_size type: integer + - description: League ID Filter + in: query + name: league_id + type: string + - description: Sport ID Filter + in: query + name: sport_id + type: string produces: - application/json responses: diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 34f74a2..11a44ad 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -242,6 +242,22 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err return i, err } +const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec +UPDATE bet_outcomes +SET status = $1 +WHERE id = $2 +` + +type UpdateBetOutcomeStatusParams struct { + Status int32 `json:"status"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutcomeStatusParams) error { + _, err := q.db.Exec(ctx, UpdateBetOutcomeStatus, arg.Status, arg.ID) + return err +} + const UpdateCashOut = `-- name: UpdateCashOut :exec UPDATE bets SET cashed_out = $2, diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go new file mode 100644 index 0000000..fb83066 --- /dev/null +++ b/gen/db/company.sql.go @@ -0,0 +1,122 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: company.sql + +package dbgen + +import ( + "context" +) + +const CreateCompany = `-- name: CreateCompany :one +INSERT INTO companies ( + name, + admin_id, + wallet_id + ) +VALUES ($1, $2, $3) +RETURNING id, name, admin_id, wallet_id +` + +type CreateCompanyParams struct { + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` +} + +func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (Company, error) { + row := q.db.QueryRow(ctx, CreateCompany, arg.Name, arg.AdminID, arg.WalletID) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} + +const DeleteCompany = `-- name: DeleteCompany :exec +DELETE FROM companies +WHERE id = $1 +` + +func (q *Queries) DeleteCompany(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, DeleteCompany, id) + return err +} + +const GetAllCompanies = `-- name: GetAllCompanies :many +SELECT id, name, admin_id, wallet_id +FROM companies +` + +func (q *Queries) GetAllCompanies(ctx context.Context) ([]Company, error) { + rows, err := q.db.Query(ctx, GetAllCompanies) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Company + for rows.Next() { + var i Company + if err := rows.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCompanyByID = `-- name: GetCompanyByID :one +SELECT id, name, admin_id, wallet_id +FROM companies +WHERE id = $1 +` + +func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (Company, error) { + row := q.db.QueryRow(ctx, GetCompanyByID, id) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} + +const UpdateCompany = `-- name: UpdateCompany :one +UPDATE companies +SET name = $1, + admin_id = $2 +WHERE id = $3 +RETURNING id, name, admin_id, wallet_id +` + +type UpdateCompanyParams struct { + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (Company, error) { + row := q.db.QueryRow(ctx, UpdateCompany, arg.Name, arg.AdminID, arg.ID) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index 4654a3e..16fad8f 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -110,13 +110,23 @@ SELECT id, FROM events WHERE is_live = false AND status = 'upcoming' + AND ( + league_id = $3 + OR $3 IS NULL + ) + AND ( + sport_id = $4 + OR $4 IS NULL + ) ORDER BY start_time ASC LIMIT $1 OFFSET $2 ` type GetPaginatedUpcomingEventsParams struct { - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` + LeagueID pgtype.Text `json:"league_id"` + SportID pgtype.Text `json:"sport_id"` } type GetPaginatedUpcomingEventsRow struct { @@ -139,7 +149,12 @@ type GetPaginatedUpcomingEventsRow struct { } func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) { - rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents, arg.Limit, arg.Offset) + rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents, + arg.Limit, + arg.Offset, + arg.LeagueID, + arg.SportID, + ) if err != nil { return nil, err } @@ -180,10 +195,23 @@ SELECT COUNT(*) FROM events WHERE is_live = false AND status = 'upcoming' + AND ( + league_id = $1 + OR $1 IS NULL + ) + AND ( + sport_id = $2 + OR $2 IS NULL + ) ` -func (q *Queries) GetTotalEvents(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, GetTotalEvents) +type GetTotalEventsParams struct { + LeagueID pgtype.Text `json:"league_id"` + SportID pgtype.Text `json:"sport_id"` +} + +func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) { + row := q.db.QueryRow(ctx, GetTotalEvents, arg.LeagueID, arg.SportID) var count int64 err := row.Scan(&count) return count, err diff --git a/gen/db/models.go b/gen/db/models.go index 4b297d6..1be7269 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -37,6 +37,7 @@ type BetOutcome struct { OddName string `json:"odd_name"` OddHeader string `json:"odd_header"` OddHandicap string `json:"odd_handicap"` + Status int32 `json:"status"` Expires pgtype.Timestamp `json:"expires"` } @@ -97,6 +98,13 @@ type BranchOperation struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } +type Company struct { + ID int64 `json:"id"` + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` +} + type CustomerWallet struct { ID int64 `json:"id"` CustomerID int64 `json:"customer_id"` @@ -215,6 +223,7 @@ type TicketOutcome struct { OddName string `json:"odd_name"` OddHeader string `json:"odd_header"` OddHandicap string `json:"odd_handicap"` + Status int32 `json:"status"` Expires pgtype.Timestamp `json:"expires"` } diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index 2dc219c..054372d 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -133,7 +133,7 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcom } const GetTicketOutcome = `-- name: GetTicketOutcome :many -SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, expires +SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires FROM ticket_outcomes WHERE ticket_id = $1 ` @@ -160,6 +160,7 @@ func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]Ticke &i.OddName, &i.OddHeader, &i.OddHandicap, + &i.Status, &i.Expires, ); err != nil { return nil, err @@ -171,3 +172,19 @@ func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]Ticke } return items, nil } + +const UpdateTicketOutcomeStatus = `-- name: UpdateTicketOutcomeStatus :exec +UPDATE ticket_outcomes +SET status = $1 +WHERE id = $2 +` + +type UpdateTicketOutcomeStatusParams struct { + Status int32 `json:"status"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateTicketOutcomeStatus(ctx context.Context, arg UpdateTicketOutcomeStatusParams) error { + _, err := q.db.Exec(ctx, UpdateTicketOutcomeStatus, arg.Status, arg.ID) + return err +} diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 1fe05f2..af9f03b 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -1,22 +1,24 @@ package domain -import "time" +import ( + "time" +) type BetOutcome struct { - 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"` - OddHeader string `json:"odd_header" example:"1"` - OddHandicap string `json:"odd_handicap" example:"1"` - - Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` + 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"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Status OutcomeStatus `json:"status" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` } type CreateBetOutcome struct { @@ -34,22 +36,13 @@ type CreateBetOutcome struct { Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` } -type BetStatus int - -const ( - BET_STATUS_PENDING BetStatus = iota - BET_STATUS_WIN - BET_STATUS_LOSS - BET_STATUS_ERROR -) - // If it is a ShopBet then UserID will be the cashier // If it is a DigitalBet then UserID will be the user and the branchID will be 0 or nil type Bet struct { ID int64 Amount Currency TotalOdds float32 - Status BetStatus + Status OutcomeStatus FullName string PhoneNumber string BranchID ValidInt64 // Can Be Nullable @@ -63,7 +56,7 @@ type GetBet struct { ID int64 Amount Currency TotalOdds float32 - Status BetStatus + Status OutcomeStatus FullName string PhoneNumber string BranchID ValidInt64 // Can Be Nullable @@ -77,7 +70,7 @@ type GetBet struct { type CreateBet struct { Amount Currency TotalOdds float32 - Status BetStatus + Status OutcomeStatus FullName string PhoneNumber string BranchID ValidInt64 // Can Be Nullable @@ -86,8 +79,3 @@ type CreateBet struct { CashoutID string } -func (b BetStatus) String() string { - return []string{"Pending", "Win", "Loss", "Error"}[b] -} - -// func isBetStatusValid() diff --git a/internal/domain/common.go b/internal/domain/common.go index 88273f3..e3a5e52 100644 --- a/internal/domain/common.go +++ b/internal/domain/common.go @@ -35,7 +35,17 @@ func (m Currency) String() string { x := float32(m) x = x / 100 return fmt.Sprintf("$%.2f", x) - } +type OutcomeStatus int +const ( + OUTCOME_STATUS_PENDING OutcomeStatus = iota + OUTCOME_STATUS_WIN + OUTCOME_STATUS_LOSS + OUTCOME_STATUS_ERROR +) + +func (b OutcomeStatus) String() string { + return []string{"Pending", "Win", "Loss", "Error"}[b] +} diff --git a/internal/domain/company.go b/internal/domain/company.go index cf2a807..989f306 100644 --- a/internal/domain/company.go +++ b/internal/domain/company.go @@ -4,7 +4,18 @@ package domain // they are the ones that manage the branches and branch managers // they will have their own wallet that they will use to distribute to the branch wallets type Company struct { - ID int64 - Name string - -} \ No newline at end of file + ID int64 + Name string + AdminID int64 + WalletID int64 +} +type CreateCompany struct { + Name string + AdminID int64 + WalletID int64 +} + +type UpdateCompany struct { + Name string + AdminID int64 +} diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index 8fedd64..15dd180 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -3,19 +3,20 @@ 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"` - 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"` - OddID int64 `json:"odd_id" example:"1"` - Odd float32 `json:"odd" example:"1.5"` - OddName string `json:"odd_name" example:"1"` - OddHeader string `json:"odd_header" example:"1"` - OddHandicap string `json:"odd_handicap" example:"1"` - Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` + ID int64 `json:"id" example:"1"` + TicketID int64 `json:"ticket_id" example:"1"` + EventID int64 `json:"event_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"` + OddID int64 `json:"odd_id" example:"1"` + Odd float32 `json:"odd" example:"1.5"` + OddName string `json:"odd_name" example:"1"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Status OutcomeStatus `json:"status" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` } type CreateTicketOutcome struct { diff --git a/internal/repository/bet.go b/internal/repository/bet.go index b486756..c19db94 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -14,7 +14,7 @@ func convertDBBet(bet dbgen.Bet) domain.Bet { ID: bet.ID, Amount: domain.Currency(bet.Amount), TotalOdds: bet.TotalOdds, - Status: domain.BetStatus(bet.Status), + Status: domain.OutcomeStatus(bet.Status), FullName: bet.FullName, PhoneNumber: bet.PhoneNumber, BranchID: domain.ValidInt64{ @@ -48,6 +48,7 @@ func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { OddName: outcome.OddName, OddHeader: outcome.OddHeader, OddHandicap: outcome.OddHandicap, + Status: domain.OutcomeStatus(outcome.Status), Expires: outcome.Expires.Time, }) } @@ -55,7 +56,7 @@ func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { ID: bet.ID, Amount: domain.Currency(bet.Amount), TotalOdds: bet.TotalOdds, - Status: domain.BetStatus(bet.Status), + Status: domain.OutcomeStatus(bet.Status), FullName: bet.FullName, PhoneNumber: bet.PhoneNumber, BranchID: domain.ValidInt64{ @@ -197,7 +198,7 @@ func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) err return err } -func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error { +func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { err := s.queries.UpdateStatus(ctx, dbgen.UpdateStatusParams{ ID: id, Status: int32(status), @@ -205,6 +206,14 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.BetSta return err } +func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{ + Status: int32(status), + ID: id, + }) + return err +} + func (s *Store) DeleteBet(ctx context.Context, id int64) error { return s.queries.DeleteBet(ctx, id) } diff --git a/internal/repository/company.go b/internal/repository/company.go new file mode 100644 index 0000000..0e52cb6 --- /dev/null +++ b/internal/repository/company.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams { + return dbgen.CreateCompanyParams{ + Name: company.Name, + AdminID: company.AdminID, + WalletID: company.WalletID, + } +} + +func convertDBCompany(dbCompany dbgen.Company) domain.Company { + return domain.Company{ + ID: dbCompany.ID, + Name: dbCompany.Name, + AdminID: dbCompany.AdminID, + WalletID: dbCompany.WalletID, + } +} + +func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { + dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company)) + if err != nil { + return domain.Company{}, err + } + return convertDBCompany(dbCompany), nil +} + +func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { + dbCompanies, err := s.queries.GetAllCompanies(ctx) + if err != nil { + return nil, err + } + + var companies []domain.Company = make([]domain.Company, 0, len(dbCompanies)) + for _, dbCompany := range dbCompanies { + companies = append(companies, convertDBCompany(dbCompany)) + } + + return companies, nil +} + +func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { + dbCompany, err := s.queries.GetCompanyByID(ctx, id) + + if err != nil { + return domain.Company{}, err + } + return convertDBCompany(dbCompany), nil +} + +func (s *Store) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { + dbCompany, err := s.queries.UpdateCompany(ctx, dbgen.UpdateCompanyParams{ + ID: id, + Name: company.Name, + AdminID: company.AdminID, + }) + + if err != nil { + return domain.Company{}, err + } + + return convertDBCompany(dbCompany), nil +} + +func (s *Store) DeleteCompany(ctx context.Context, id int64) error { + return s.queries.DeleteCompany(ctx, id) +} diff --git a/internal/repository/event.go b/internal/repository/event.go index d0ff7d6..b64973d 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -2,6 +2,7 @@ package repository import ( "context" + "math" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" @@ -87,8 +88,16 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven } return upcomingEvents, nil } -func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error) { +func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) { events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{ + LeagueID: pgtype.Text{ + String: leagueID.Value, + Valid: leagueID.Valid, + }, + SportID: pgtype.Text{ + String: sportID.Value, + Valid: sportID.Valid, + }, Limit: limit, Offset: offset * limit, }) @@ -115,13 +124,22 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off StartTime: e.StartTime.Time.UTC(), } } - totalCount, err := s.queries.GetTotalEvents(ctx) + totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ + LeagueID: pgtype.Text{ + String: leagueID.Value, + Valid: leagueID.Valid, + }, + SportID: pgtype.Text{ + String: sportID.Value, + Valid: sportID.Valid, + }, + }) if err != nil { return nil, 0, err } - numberOfPages := (totalCount) / int64(limit) - return upcomingEvents, numberOfPages, nil + numberOfPages := math.Ceil(float64(totalCount) / float64(limit)) + return upcomingEvents, int64(numberOfPages), nil } func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { event, err := s.queries.GetUpcomingByID(ctx, ID) diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index 911ad9e..5083f65 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -34,6 +34,7 @@ func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket { OddName: outcome.OddName, OddHeader: outcome.OddHeader, OddHandicap: outcome.OddHandicap, + Status: domain.OutcomeStatus(outcome.Status), Expires: outcome.Expires.Time, }) } @@ -122,6 +123,14 @@ func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { return result, nil } +func (s *Store) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + err := s.queries.UpdateTicketOutcomeStatus(ctx, dbgen.UpdateTicketOutcomeStatusParams{ + Status: int32(status), + ID: id, + }) + return err +} + func (s *Store) DeleteOldTickets(ctx context.Context) error { return s.queries.DeleteOldTickets(ctx) } diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 8066c50..d5ea609 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -14,6 +14,7 @@ type BetStore interface { GetAllBets(ctx context.Context) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error - UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error + UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error + UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error DeleteBet(ctx context.Context, id int64) error } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index a464094..1ff9565 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -17,6 +17,7 @@ func NewService(betStore BetStore) *Service { } func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { + return s.betStore.CreateBet(ctx, bet) } @@ -42,10 +43,14 @@ func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) e return s.betStore.UpdateCashOut(ctx, id, cashedOut) } -func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.BetStatus) error { +func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { return s.betStore.UpdateStatus(ctx, id, status) } +func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + return s.betStore.UpdateBetOutcomeStatus(ctx, id, status) +} + func (s *Service) DeleteBet(ctx context.Context, id int64) error { return s.betStore.DeleteBet(ctx, id) } diff --git a/internal/services/company/port.go b/internal/services/company/port.go new file mode 100644 index 0000000..5263ed1 --- /dev/null +++ b/internal/services/company/port.go @@ -0,0 +1,15 @@ +package company + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type CompanyStore interface { + CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) + GetAllCompanies(ctx context.Context) ([]domain.Company, error) + GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) + UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) + DeleteCompany(ctx context.Context, id int64) error +} diff --git a/internal/services/company/service.go b/internal/services/company/service.go new file mode 100644 index 0000000..b5b6f7e --- /dev/null +++ b/internal/services/company/service.go @@ -0,0 +1,35 @@ +package company + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + companyStore CompanyStore +} + +func NewService(companyStore CompanyStore) *Service { + return &Service{ + companyStore: companyStore, + } +} + +func (s *Service) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { + return s.companyStore.CreateCompany(ctx, company) +} +func (s *Service) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { + return s.companyStore.GetAllCompanies(ctx) +} + +func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { + return s.companyStore.GetCompanyByID(ctx, id) +} + +func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { + return s.companyStore.UpdateCompany(ctx, id, company) +} +func (s *Service) DeleteCompany(ctx context.Context, id int64) error { + return s.companyStore.DeleteCompany(ctx, id) +} diff --git a/internal/services/event/port.go b/internal/services/event/port.go index e1c3c64..1404b09 100644 --- a/internal/services/event/port.go +++ b/internal/services/event/port.go @@ -10,6 +10,6 @@ type Service interface { FetchLiveEvents(ctx context.Context) error FetchUpcomingEvents(ctx context.Context) error GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) - GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error) + GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 5ab1ceb..4eb5601 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -178,8 +178,8 @@ func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEv return s.store.GetAllUpcomingEvents(ctx) } -func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32) ([]domain.UpcomingEvent, int64, error) { - return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset) +func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) { + return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID) } func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { diff --git a/internal/services/ticket/port.go b/internal/services/ticket/port.go index ae531c6..930026e 100644 --- a/internal/services/ticket/port.go +++ b/internal/services/ticket/port.go @@ -11,6 +11,7 @@ type TicketStore interface { CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) + UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error DeleteOldTickets(ctx context.Context) error DeleteTicket(ctx context.Context, id int64) error } diff --git a/internal/services/ticket/service.go b/internal/services/ticket/service.go index 46036f4..1d86313 100644 --- a/internal/services/ticket/service.go +++ b/internal/services/ticket/service.go @@ -30,6 +30,10 @@ func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { return s.ticketStore.GetAllTickets(ctx) } + +func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + return s.ticketStore.UpdateTicketOutcomeStatus(ctx, id, status) +} func (s *Service) DeleteTicket(ctx context.Context, id int64) error { return s.ticketStore.DeleteTicket(ctx, id) } diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index 2644a39..ced664d 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -91,6 +91,8 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain. return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) } + + func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { return s.walletStore.UpdateWalletActive(ctx, id, isActive) } diff --git a/internal/web_server/app.go b/internal/web_server/app.go index b41dcf0..d999648 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -7,6 +7,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" @@ -34,6 +35,7 @@ type App struct { walletSvc *wallet.Service transactionSvc *transaction.Service branchSvc *branch.Service + companySvc *company.Service validator *customvalidator.CustomValidator JwtConfig jwtutil.JwtConfig Logger *slog.Logger @@ -52,6 +54,7 @@ func NewApp( walletSvc *wallet.Service, transactionSvc *transaction.Service, branchSvc *branch.Service, + companySvc *company.Service, notidicationStore notificationservice.NotificationStore, prematchSvc *odds.ServiceImpl, eventSvc event.Service, @@ -83,6 +86,7 @@ func NewApp( walletSvc: walletSvc, transactionSvc: transactionSvc, branchSvc: branchSvc, + companySvc: companySvc, NotidicationStore: notidicationStore, Logger: logger, prematchSvc: prematchSvc, diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index b04a9f3..a6b07d4 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -21,82 +21,47 @@ import ( ) type CreateBetOutcomeReq struct { - // BetID int64 `json:"bet_id" example:"1"` EventID int64 `json:"event_id" example:"1"` OddID int64 `json:"odd_id" example:"1"` MarketID int64 `json:"market_id" example:"1"` - // HomeTeamName string `json:"home_team_name" example:"Manchester"` - // AwayTeamName string `json:"away_team_name" example:"Liverpool"` - // 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 -} - -func (n *NullableInt64) UnmarshalJSON(data []byte) error { - if string(data) == "null" { - n.Valid = false - return nil - } - - var value int64 - if err := json.Unmarshal(data, &value); err != nil { - return err - } - - n.Value = value - n.Valid = true - return nil -} - -func (n NullableInt64) MarshalJSON() ([]byte, error) { - if !n.Valid { - return []byte("null"), nil - } - return json.Marshal(n.Value) } type CreateBetReq struct { 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"` + Status domain.OutcomeStatus `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 { - ID int64 `json:"id" example:"1"` - 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"` - BranchID int64 `json:"branch_id" example:"2"` - 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"` + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status domain.OutcomeStatus `json:"status" example:"1"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + BranchID int64 `json:"branch_id" example:"2"` + 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"` - Outcomes []domain.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"` - 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"` + ID int64 `json:"id" example:"1"` + Outcomes []domain.BetOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status domain.OutcomeStatus `json:"status" example:"1"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + 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 { @@ -152,15 +117,12 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, if err := c.BodyParser(&req); err != nil { logger.Error("CreateBetReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } // Validating user by role @@ -174,9 +136,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, branch, err := branchSvc.GetBranchByCashier(c.Context(), user.ID) if err != nil { logger.Error("CreateBetReq failed, branch id invalid") - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Branch ID invalid", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } // Deduct a percentage of the amount @@ -186,9 +146,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, if err != nil { logger.Error("CreateBetReq failed, unable to deduct from WalletID") - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Unable to deduct from branch wallet", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } bet, err = betSvc.CreateBet(c.Context(), domain.CreateBet{ @@ -240,8 +198,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, // TODO Validate Outcomes Here and make sure they didn't expire // Validation for creating tickets if len(req.Outcomes) > 30 { - response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) } var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes)) for _, outcome := range req.Outcomes { @@ -250,22 +207,19 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, oddIDStr := strconv.FormatInt(outcome.OddID, 10) event, err := eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr) if err != nil { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) } // Checking to make sure the event hasn't already started currentTime := time.Now() if event.StartTime.Before(currentTime) { - response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) } odds, err := oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) if err != nil { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) } type rawOddType struct { ID string @@ -291,8 +245,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, } if !isOddFound { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) } parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) @@ -454,15 +407,12 @@ func UpdateCashOut(logger *slog.Logger, betSvc *bet.Service, var req UpdateCashOutReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateCashOutReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } err = betSvc.UpdateCashOut(c.Context(), id, req.CashedOut) diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index ab97316..0741049 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -116,15 +116,12 @@ func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, walletSvc *wal if err := c.BodyParser(&req); err != nil { logger.Error("CreateBranchReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } // Create Branch Wallet @@ -194,14 +191,12 @@ func CreateSupportedOperation(logger *slog.Logger, branchSvc *branch.Service, va if err := c.BodyParser(&req); err != nil { logger.Error("CreateSupportedOperationReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } operation, err := branchSvc.CreateSupportedOperation(c.Context(), domain.CreateSupportedOperation{ Name: req.Name, @@ -241,15 +236,12 @@ func CreateBranchOperation(logger *slog.Logger, branchSvc *branch.Service, valid var req CreateBranchOperationReq if err := c.BodyParser(&req); err != nil { logger.Error("CreateBranchOperationReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } err := branchSvc.CreateBranchOperation(c.Context(), domain.CreateBranchOperation{ @@ -383,6 +375,7 @@ func GetBranchByCompanyID(logger *slog.Logger, branchSvc *branch.Service, valida // @Router /branch [get] func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { + // TODO: Limit the get all branches to only the companies for branch manager and cashiers branches, err := branchSvc.GetAllBranches(c.Context()) if err != nil { logger.Error("Failed to get branches", "error", err) @@ -566,14 +559,11 @@ func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus if err := c.BodyParser(&req); err != nil { logger.Error("CreateBranchReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } branch, err := branchSvc.UpdateBranch(c.Context(), id, domain.UpdateBranch{ diff --git a/internal/web_server/handlers/cashier.go b/internal/web_server/handlers/cashier.go index b2b6418..ffce3ae 100644 --- a/internal/web_server/handlers/cashier.go +++ b/internal/web_server/handlers/cashier.go @@ -40,14 +40,11 @@ func CreateCashier(logger *slog.Logger, userSvc *user.Service, branchSvc *branch var req CreateCashierReq if err := c.BodyParser(&req); err != nil { logger.Error("RegisterUser failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } userRequest := domain.CreateUserReq{ FirstName: req.FirstName, @@ -60,19 +57,15 @@ func CreateCashier(logger *slog.Logger, userSvc *user.Service, branchSvc *branch newUser, err := userSvc.CreateUser(c.Context(), userRequest) if err != nil { logger.Error("CreateCashier failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) } err = branchSvc.CreateBranchCashier(c.Context(), req.BranchID, newUser.ID) if err != nil { logger.Error("CreateCashier failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) } - - response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil) } } @@ -119,15 +112,13 @@ func GetAllCashiers(logger *slog.Logger, userSvc *user.Service, validator *custo // } // valErrs, ok := validator.Validate(c, filter) // if !ok { - // response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - // return nil + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) // } cashiers, err := userSvc.GetAllCashiers(c.Context()) if err != nil { logger.Error("GetAllCashiers failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) } var result []GetCashierRes @@ -148,8 +139,8 @@ func GetAllCashiers(logger *slog.Logger, userSvc *user.Service, validator *custo Suspended: cashier.Suspended, }) } - response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil) - return nil + + return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil) } } @@ -177,22 +168,19 @@ func UpdateCashier(logger *slog.Logger, userSvc *user.Service, validator *custom var req updateUserReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateCashier failed", "error", err) - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } cashierIdStr := c.Params("id") cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64) if err != nil { logger.Error("UpdateCashier failed", "error", err) - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) } err = userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ UserId: cashierId, @@ -212,11 +200,9 @@ func UpdateCashier(logger *slog.Logger, userSvc *user.Service, validator *custom ) if err != nil { logger.Error("UpdateCashier failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cashier", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cashier", nil, nil) } - response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil) } } diff --git a/internal/web_server/handlers/company_handler.go b/internal/web_server/handlers/company_handler.go new file mode 100644 index 0000000..0fb9449 --- /dev/null +++ b/internal/web_server/handlers/company_handler.go @@ -0,0 +1,229 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type CreateCompanyReq struct { + Name string `json:"name" example:"CompanyName"` + AdminID int64 `json:"admin_id" example:"1"` +} + +type CompanyRes struct { + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"CompanyName"` + AdminID int64 `json:"admin_id" example:"1"` + WalletID int64 `json:"wallet_id" example:"1"` +} + +func convertCompany(company domain.Company) CompanyRes { + return CompanyRes{ + ID: company.ID, + Name: company.Name, + AdminID: company.AdminID, + WalletID: company.WalletID, + } +} + +// CreateCompany godoc +// @Summary Create a company +// @Description Creates a company +// @Tags company +// @Accept json +// @Produce json +// @Param createCompany body CreateCompanyReq true "Creates company" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company [post] +func CreateCompany(logger *slog.Logger, companySvc *company.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateCompanyReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + // Create Branch Wallet + newWallet, err := walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + IsTransferable: true, + UserID: req.AdminID, + }) + + if err != nil { + logger.Error("Create Company Wallet failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create company wallet", err, nil) + } + + company, err := companySvc.CreateCompany(c.Context(), domain.CreateCompany{ + Name: req.Name, + AdminID: req.AdminID, + WalletID: newWallet.ID, + }) + + if err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusCreated, "Company Created", res, nil) + + } +} + +// GetAllCompanies godoc +// @Summary Gets all companies +// @Description Gets all companies +// @Tags company +// @Accept json +// @Produce json +// @Success 200 {array} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company [get] +func GetAllCompanies(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companies, err := companySvc.GetAllCompanies(c.Context()) + if err != nil { + logger.Error("Failed to get companies", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) + } + + var result []CompanyRes = make([]CompanyRes, 0, len(companies)) + + for _, company := range companies { + result = append(result, convertCompany(company)) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil) + } +} + +// GetCompanyByID godoc +// @Summary Gets company by id +// @Description Gets a single company by id +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [get] +func GetCompanyByID(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + if err != nil { + logger.Error("Invalid company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) + } + + company, err := companySvc.GetCompanyByID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get company by ID", "companyID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) + } +} + +// UpdateCompany godoc +// @Summary Updates a company +// @Description Updates a company +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Param updateCompany body CreateCompanyReq true "Update Company" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [put] +func UpdateCompany(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + if err != nil { + logger.Error("Invalid company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) + } + + var req CreateCompanyReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + company, err := companySvc.UpdateCompany(c.Context(), id, domain.UpdateCompany{ + Name: req.Name, + AdminID: req.AdminID, + }) + + if err != nil { + logger.Error("Failed to update company", "companyID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", err, nil) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusOK, "Company Updated", res, nil) + } +} + +// DeleteCompany godoc +// @Summary Delete the company +// @Description Delete the company +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID"" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [delete] +func DeleteCompany(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + + if err != nil { + logger.Error("Invalid Company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Company ID", err, nil) + } + + err = companySvc.DeleteCompany(c.Context(), id) + if err != nil { + logger.Error("Failed to delete by ID", "Company ID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Company", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Company removed successfully", nil, nil) + } +} diff --git a/internal/web_server/handlers/manager.go b/internal/web_server/handlers/manager.go index be428c9..577420d 100644 --- a/internal/web_server/handlers/manager.go +++ b/internal/web_server/handlers/manager.go @@ -36,14 +36,11 @@ func CreateManager(logger *slog.Logger, userSvc *user.Service, validator *custom var req CreateManagerReq if err := c.BodyParser(&req); err != nil { logger.Error("RegisterUser failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } user := domain.CreateUserReq{ FirstName: req.FirstName, @@ -56,11 +53,9 @@ func CreateManager(logger *slog.Logger, userSvc *user.Service, validator *custom _, err := userSvc.CreateUser(c.Context(), user) if err != nil { logger.Error("CreateManagers failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create Managers", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create Managers", nil, nil) } - response.WriteJSON(c, fiber.StatusOK, "Managers created successfully", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusOK, "Managers created successfully", nil, nil) } } @@ -91,17 +86,15 @@ func GetAllManagers(logger *slog.Logger, userSvc *user.Service, validator *custo } valErrs, ok := validator.Validate(c, filter) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } Managers, err := userSvc.GetAllUsers(c.Context(), filter) if err != nil { logger.Error("GetAllManagers failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil) } - response.WriteJSON(c, fiber.StatusOK, "Managers retrieved successfully", Managers, nil) - return nil + + return response.WriteJSON(c, fiber.StatusOK, "Managers retrieved successfully", Managers, nil) } } @@ -123,22 +116,19 @@ func UPdateManagers(logger *slog.Logger, userSvc *user.Service, validator *custo var req updateUserReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateManagers failed", "error", err) - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } ManagersIdStr := c.Params("id") ManagersId, err := strconv.ParseInt(ManagersIdStr, 10, 64) if err != nil { logger.Error("UpdateManagers failed", "error", err) - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil) } err = userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ UserId: ManagersId, @@ -158,11 +148,9 @@ func UPdateManagers(logger *slog.Logger, userSvc *user.Service, validator *custo ) if err != nil { logger.Error("UpdateManagers failed", "error", err) - response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Managers", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Managers", nil, nil) } - response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil) } } diff --git a/internal/web_server/handlers/prematch.go b/internal/web_server/handlers/prematch.go index 493bf94..10df2a5 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -4,6 +4,7 @@ import ( "log/slog" "strconv" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" @@ -98,6 +99,8 @@ func GetRawOddsByMarketID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fi // @Produce json // @Param page query int false "Page number" // @Param page_size query int false "Page size" +// @Param league_id query string false "League ID Filter" +// @Param sport_id query string false "Sport ID Filter" // @Success 200 {array} domain.UpcomingEvent // @Failure 500 {object} response.APIResponse // @Router /prematch/events [get] @@ -105,8 +108,20 @@ func GetAllUpcomingEvents(logger *slog.Logger, eventSvc event.Service) fiber.Han return func(c *fiber.Ctx) error { page := c.QueryInt("page", 1) pageSize := c.QueryInt("page_size", 10) + leagueIDQuery := c.Query("league_id") + sportIDQuery := c.Query("sport_id") + + leagueID := domain.ValidString{ + Value: leagueIDQuery, + Valid: leagueIDQuery != "", + } + sportID := domain.ValidString{ + Value: sportIDQuery, + Valid: sportIDQuery != "", + } + + events, total, err := eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page)-1, leagueID, sportID) - events, total, err := eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page) - 1) if err != nil { return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil) } diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index bf1bb87..5011f24 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -38,6 +38,12 @@ type CreateTicketRes struct { FastCode int64 `json:"fast_code" example:"1234"` CreatedNumber int64 `json:"created_number" example:"3"` } +type TicketRes struct { + ID int64 `json:"id" example:"1"` + Outcomes []domain.TicketOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` +} // CreateTicket godoc // @Summary Create a temporary ticket @@ -55,22 +61,18 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, eventSvc event var req CreateTicketReq if err := c.BodyParser(&req); err != nil { logger.Error("CreateTicketReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } // TODO Validate Outcomes Here and make sure they didn't expire // Validation for creating tickets if len(req.Outcomes) > 30 { - response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) } var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) for _, outcome := range req.Outcomes { @@ -79,22 +81,19 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, eventSvc event oddIDStr := strconv.FormatInt(outcome.OddID, 10) event, err := eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr) if err != nil { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) } // Checking to make sure the event hasn't already started currentTime := time.Now() if event.StartTime.Before(currentTime) { - response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) } odds, err := oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) if err != nil { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) } type rawOddType struct { ID string @@ -120,8 +119,7 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, eventSvc event } if !isOddFound { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) } parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) @@ -173,13 +171,6 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, eventSvc event } } -type TicketRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []domain.TicketOutcome `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` -} - // GetTicketByID godoc // @Summary Get ticket by ID // @Description Retrieve ticket details by ticket ID diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index 7cf85e1..3819c3f 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -97,9 +97,7 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, 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", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "This user type doesn't have branches", err, nil) } branchID = branch.ID @@ -108,9 +106,7 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, 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", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil) } branchID = branch.ID } @@ -118,16 +114,13 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, var req CreateTransactionReq if err := c.BodyParser(&req); err != nil { logger.Error("CreateTransactionReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } 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 + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ @@ -288,15 +281,12 @@ func UpdateTransactionVerified(logger *slog.Logger, transactionSvc *transaction. var req UpdateTransactionVerifiedReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateTransactionVerifiedReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } err = transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified) diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go index 983164e..df85550 100644 --- a/internal/web_server/handlers/transfer_handler.go +++ b/internal/web_server/handlers/transfer_handler.go @@ -158,22 +158,18 @@ func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc if err := c.BodyParser(&req); err != nil { logger.Error("CreateTransferReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } transfer, err := walletSvc.TransferToWallet(c.Context(), senderID, receiverID, domain.ToCurrency(req.Amount), domain.PaymentMethod(req.PaymentMethod), domain.ValidInt64{Value: userID, Valid: true}) if !ok { - response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) } res := convertTransfer(transfer) @@ -218,15 +214,12 @@ func RefillWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *cus if err := c.BodyParser(&req); err != nil { logger.Error("CreateRefillReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := validator.Validate(c, req) if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } transfer, err := walletSvc.RefillWallet(c.Context(), domain.CreateTransfer{ @@ -241,8 +234,7 @@ func RefillWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *cus }) if !ok { - response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) - return nil + return response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) } res := convertTransfer(transfer) diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index 37b72f4..e74d91f 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -213,9 +213,7 @@ func UpdateWalletActive(logger *slog.Logger, walletSvc *wallet.Service, validato var req UpdateWalletActiveReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateWalletActiveReq failed", "error", err) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "Invalid request", - }) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } err = walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive) @@ -249,7 +247,7 @@ func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator vendorID, err := strconv.ParseInt(c.Get("vendor_id"), 10, 64) if err != nil { - return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id") + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company_id", err, nil) } logger.Info("Company ID: " + strconv.FormatInt(vendorID, 10)) diff --git a/internal/web_server/middleware.go b/internal/web_server/middleware.go index aa51c1a..057295b 100644 --- a/internal/web_server/middleware.go +++ b/internal/web_server/middleware.go @@ -47,9 +47,13 @@ func (a *App) authMiddleware(c *fiber.Ctx) error { c.Locals("branch_id", claim.BranchId) c.Locals("refresh_token", refreshToken) - if claim.Role != domain.RoleCustomer { - // TODO: Add branch id here from the user - // c.Locals("branch_id", claim.) + return c.Next() +} + +func (a *App) SuperAdminOnly(c *fiber.Ctx) error { + userRole := c.Locals("role").(domain.Role) + if userRole != domain.RoleSuperAdmin { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") } return c.Next() } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index e730bc0..576ff50 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -25,7 +25,6 @@ func (a *App) initAppRoutes() { if err != nil { return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id") } - // a.logger.Info("User ID: " + string(userId.(string))) //panic: interface conversion: interface {} is int64, not string a.logger.Info("User ID: " + strconv.FormatInt(userId, 10)) fmt.Printf("User ID: %d\n", userId) a.logger.Info("Role: " + role) @@ -85,6 +84,13 @@ func (a *App) initAppRoutes() { a.fiber.Get("/branch/:id/operation", a.authMiddleware, handlers.GetBranchOperations(a.logger, a.branchSvc, a.validator)) a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, handlers.DeleteBranchOperation(a.logger, a.branchSvc, a.validator)) + // Company + a.fiber.Post("/company", a.authMiddleware, a.SuperAdminOnly, handlers.CreateCompany(a.logger, a.companySvc, a.walletSvc, a.validator)) + a.fiber.Get("/company", a.authMiddleware, a.SuperAdminOnly, handlers.GetAllCompanies(a.logger, a.companySvc, a.validator)) + a.fiber.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.GetCompanyByID(a.logger, a.companySvc, a.validator)) + a.fiber.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.UpdateCompany(a.logger, a.companySvc, a.validator)) + a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.DeleteCompany(a.logger, a.companySvc, a.validator)) + // Ticket a.fiber.Post("/ticket", handlers.CreateTicket(a.logger, a.ticketSvc, a.eventSvc, *a.prematchSvc, a.validator)) a.fiber.Get("/ticket", handlers.GetAllTickets(a.logger, a.ticketSvc, a.validator))