From 1b0a068a02d436517154b06eb4ab7b42899b3f67 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Fri, 11 Apr 2025 21:46:48 +0300 Subject: [PATCH] major fixes while integrating --- db/migrations/000001_fortune.up.sql | 88 ++++- db/query/bet.sql | 45 ++- db/query/branch.sql | 98 +++-- db/query/ticket.sql | 27 +- db/query/user.sql | 111 ++++-- db/query/wallet.sql | 72 ++-- docs/docs.go | 363 +++++++++++++++--- docs/swagger.json | 363 +++++++++++++++--- docs/swagger.yaml | 244 ++++++++++-- gen/db/bet.sql.go | 129 ++++++- gen/db/branch.sql.go | 198 +++++++++- gen/db/copyfrom.go | 78 ++++ gen/db/db.go | 1 + gen/db/models.go | 46 +++ gen/db/ticket.sql.go | 74 +++- gen/db/user.sql.go | 103 ++++- gen/db/wallet.sql.go | 108 +++++- internal/domain/bet.go | 30 +- internal/domain/common.go | 2 + internal/domain/ticket.go | 22 +- internal/domain/user.go | 1 - internal/domain/wallet.go | 45 ++- internal/repository/bet.go | 85 +++- internal/repository/branch.go | 27 ++ internal/repository/ticket.go | 57 ++- internal/repository/user.go | 76 +++- internal/repository/wallet.go | 25 ++ internal/services/bet/port.go | 6 +- internal/services/bet/service.go | 13 +- internal/services/branch/port.go | 11 +- internal/services/branch/service.go | 13 + internal/services/ticket/port.go | 5 +- internal/services/ticket/service.go | 9 +- internal/services/user/direct.go | 23 +- internal/services/user/port.go | 4 +- internal/services/wallet/port.go | 2 +- internal/services/wallet/transfer.go | 33 ++ internal/services/wallet/wallet.go | 5 +- internal/web_server/handlers/bet_handler.go | 156 ++++++-- .../web_server/handlers/branch_handler.go | 76 ++++ internal/web_server/handlers/cashier.go | 88 +++-- internal/web_server/handlers/manager.go | 2 - .../web_server/handlers/ticket_handler.go | 44 ++- .../handlers/transaction_handler.go | 3 + .../web_server/handlers/transfer_handler.go | 162 +++++++- .../web_server/handlers/wallet_handler.go | 56 +++ internal/web_server/routes.go | 12 +- sqlc.yaml | 8 + 48 files changed, 2811 insertions(+), 438 deletions(-) create mode 100644 gen/db/copyfrom.go diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 982681a..830ad51 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -69,20 +69,26 @@ CREATE TABLE IF NOT EXISTS bet_outcomes ( id BIGSERIAL PRIMARY KEY, bet_id BIGINT NOT NULL, event_id bigint not null, - odd_id BIGINT NOT NULL, + odd_id BIGINT NOT NULL ); CREATE TABLE IF NOT EXISTS ticket_outcomes ( id BIGSERIAL PRIMARY KEY, ticket_id BIGINT NOT NULL, event_id bigint not null, - odd_id BIGINT NOT NULL, + odd_id BIGINT NOT NULL ); -ALTER TABLE bets -ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id); -ALTER TABLE bets -ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); -ALTER TABLE bet_outcomes -ADD CONSTRAINT fk_bet_outcomes_bet FOREIGN KEY (bet_id) REFERENCES bets(id); +CREATE VIEW bet_with_outcomes AS +SELECT bets.*, + JSON_AGG(bet_outcomes.*) AS outcomes +FROM bets + LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id +GROUP BY bets.id; +CREATE VIEW ticket_with_outcomes AS +SELECT tickets.*, + JSON_AGG(ticket_outcomes.*) AS outcomes +FROM tickets + LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id +GROUP BY tickets.id; CREATE TABLE IF NOT EXISTS wallets ( id BIGSERIAL PRIMARY KEY, balance BIGINT NOT NULL DEFAULT 0, @@ -164,6 +170,48 @@ CREATE TABLE IF NOT EXISTS branch_operations ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE IF NOT EXISTS branch_cashiers ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + branch_id BIGINT NOT NULL, + UNIQUE(user_id, branch_id) +); +ALTER TABLE refresh_tokens +ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); +ALTER TABLE bets +ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), + ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); +ALTER TABLE bet_outcomes +ADD CONSTRAINT fk_bet_outcomes_bets FOREIGN KEY (bet_id) REFERENCES bets(id), + ADD CONSTRAINT fk_bet_outcomes_events FOREIGN KEY (event_id) REFERENCES supported_operations(id), + ADD CONSTRAINT fk_bet_outcomes_odds FOREIGN KEY (odd_id) REFERENCES supported_operations(id); +ALTER TABLE ticket_outcomes +ADD CONSTRAINT fk_ticket_outcomes_tickets FOREIGN KEY (ticket_id) REFERENCES tickets(id), + ADD CONSTRAINT fk_ticket_outcomes_events FOREIGN KEY (event_id) REFERENCES supported_operations(id), + ADD CONSTRAINT fk_ticket_outcomes_odds FOREIGN KEY (odd_id) REFERENCES supported_operations(id); +ALTER TABLE wallets +ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); +ALTER TABLE customer_wallets +ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), + ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id), + ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id); +ALTER TABLE wallet_transfer +ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), + ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), + ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); +ALTER TABLE transactions +ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), + ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), + ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id); +ALTER TABLE branches +ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), + ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id); +ALTER TABLE branch_operations +ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id), + ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id); +ALTER TABLE branch_cashiers +ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id), + ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id); ----------------------------------------------seed data------------------------------------------------------------- -------------------------------------- DO NOT USE IN PRODUCTION------------------------------------------------- CREATE EXTENSION IF NOT EXISTS pgcrypto; @@ -215,7 +263,7 @@ VALUES ( 'cybersamt@gmail.com', NULL, crypt('password@123', gen_salt('bf'))::bytea, - 'cashier', + 'super_admin', TRUE, FALSE, CURRENT_TIMESTAMP, @@ -226,4 +274,24 @@ VALUES ( INSERT INTO supported_operations (name, description) VALUES ('SportBook', 'Sportbook operations'), ('Virtual', 'Virtual operations'), - ('GameZone', 'GameZone operations'); \ No newline at end of file + ('GameZone', 'GameZone operations'); +INSERT INTO wallets ( + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + is_active, + created_at, + updated_at + ) +VALUES ( + 10000, + TRUE, + TRUE, + TRUE, + 1, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ); \ No newline at end of file diff --git a/db/query/bet.sql b/db/query/bet.sql index 2d1d098..01230b2 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -1,16 +1,43 @@ -- name: CreateBet :one -INSERT INTO bets (amount, total_odds, status, full_name, phone_number, branch_id, user_id, is_shop_bet, cashout_id) +INSERT INTO bets ( + amount, + total_odds, + status, + full_name, + phone_number, + branch_id, + user_id, + is_shop_bet, + cashout_id + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; - +-- name: CreateBetOutcome :copyfrom +INSERT INTO bet_outcomes (bet_id, event_id, odd_id) +VALUES ($1, $2, $3); -- name: GetAllBets :many -SELECT * FROM bets; - +SELECT * +FROM bet_with_outcomes; -- name: GetBetByID :one -SELECT * FROM bets WHERE id = $1; - +SELECT * +FROM bet_with_outcomes +WHERE id = $1; +-- name: GetBetByCashoutID :many +SELECT +FROM bet_with_outcomes +WHERE cashout_id = $1; +-- name: GetBetByBranchID :many +SELECT * +FROM bet_with_outcomes +WHERE branch_id = $1; -- name: UpdateCashOut :exec -UPDATE bets SET cashed_out = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; - +UPDATE bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; -- name: DeleteBet :exec -DELETE FROM bets WHERE id = $1; +DELETE FROM bets +WHERE id = $1; +-- name: DeleteBetOutcome :exec +DELETE FROM bet_outcomes +WHERE bet_id = $1; \ No newline at end of file diff --git a/db/query/branch.sql b/db/query/branch.sql index 97b374e..041d0ef 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -1,42 +1,86 @@ - -- name: CreateBranch :one -INSERT INTO branches (name, location, wallet_id, branch_manager_id, company_id, is_self_owned) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; - +INSERT INTO branches ( + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; -- name: CreateSupportedOperation :one -INSERT INTO supported_operations (name, description) VALUES ($1, $2) RETURNING *; - +INSERT INTO supported_operations (name, description) +VALUES ($1, $2) +RETURNING *; -- name: CreateBranchOperation :one -INSERT INTO branch_operations (operation_id, branch_id) VALUES ($1, $2) RETURNING *; - +INSERT INTO branch_operations (operation_id, branch_id) +VALUES ($1, $2) +RETURNING *; +-- name: CreateBranchCashier :one +INSERT INTO branch_cashiers (user_id, branch_id) +VALUES ($1, $2) +RETURNING *; -- name: GetAllBranches :many -SELECT * FROM branch_details; +SELECT * +FROM branch_details; -- name: GetBranchByID :one -SELECT * FROM branch_details WHERE id = $1; - +SELECT * +FROM branch_details +WHERE id = $1; -- name: GetBranchByCompanyID :many -SELECT * FROM branch_details WHERE company_id = $1; - +SELECT * +FROM branch_details +WHERE company_id = $1; -- name: GetBranchByManagerID :many -SELECT * FROM branch_details WHERE branch_manager_id = $1; - +SELECT * +FROM branch_details +WHERE branch_manager_id = $1; -- name: SearchBranchByName :many -SELECT * FROM branch_details WHERE name ILIKE '%' || $1 || '%'; - +SELECT * +FROM branch_details +WHERE name ILIKE '%' || $1 || '%'; -- name: GetAllSupportedOperations :many -SELECT * FROM supported_operations; - +SELECT * +FROM supported_operations; -- name: GetBranchOperations :many -SELECT branch_operations.*, supported_operations.name, supported_operations.description -FROM branch_operations -JOIN supported_operations ON branch_operations.operation_id = supported_operations.id +SELECT branch_operations.*, + supported_operations.name, + supported_operations.description +FROM branch_operations + JOIN supported_operations ON branch_operations.operation_id = supported_operations.id WHERE branch_operations.branch_id = $1; - +-- name: GetBranchByCashier :one +SELECT branches.* +FROM branch_cashiers + JOIN branches ON branch_cashiers.branch_id = branches.id +WHERE branch_cashiers.user_id = $1; +-- name: GetCashiersByBranch :many +SELECT users.* +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1; +-- name: GetAllCashiers :many +SELECT users.* +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id; -- name: UpdateBranch :one -UPDATE branches SET name = $1, location = $2, branch_manager_id = $3, company_id = $4, is_self_owned = $5 WHERE id = $6 RETURNING *; - +UPDATE branches +SET name = $1, + location = $2, + branch_manager_id = $3, + company_id = $4, + is_self_owned = $5 +WHERE id = $6 +RETURNING *; -- name: DeleteBranch :exec -DELETE FROM branches WHERE id = $1; - +DELETE FROM branches +WHERE id = $1; -- name: DeleteBranchOperation :exec -DELETE FROM branch_operations WHERE operation_id = $1 AND branch_id = $2; +DELETE FROM branch_operations +WHERE operation_id = $1 + AND branch_id = $2; +-- name: DeleteBranchCashier :exec +DELETE FROM branch_cashiers +WHERE user_id = $1; \ No newline at end of file diff --git a/db/query/ticket.sql b/db/query/ticket.sql index 04be763..debcb48 100644 --- a/db/query/ticket.sql +++ b/db/query/ticket.sql @@ -2,15 +2,26 @@ INSERT INTO tickets (amount, total_odds) VALUES ($1, $2) RETURNING *; - +-- name: CreateTicketOutcome :copyfrom +INSERT INTO ticket_outcomes (ticket_id, event_id, odd_id) +VALUES ($1, $2, $3); -- name: GetAllTickets :many -SELECT * FROM tickets; - +SELECT * +FROM ticket_with_outcomes; -- name: GetTicketByID :one -SELECT * FROM tickets WHERE id = $1; - +SELECT * +FROM ticket_with_outcomes +WHERE id = $1; +-- name: GetTicketOutcome :many +SELECT * +FROM ticket_outcomes +WHERE ticket_id = $1; -- name: DeleteTicket :exec -DELETE FROM tickets WHERE id = $1; - +DELETE FROM tickets +WHERE id = $1; -- name: DeleteOldTickets :exec -Delete from tickets where created_at < now() - interval '1 day'; +Delete from tickets +where created_at < now() - interval '1 day'; +-- name: DeleteTicketOutcome :exec +Delete from ticket_outcomes +where ticket_id = $1; \ No newline at end of file diff --git a/db/query/user.sql b/db/query/user.sql index 8e127af..bee4713 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -1,49 +1,114 @@ -- name: CreateUser :one - -INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at) +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + role, + password, + email_verified, + phone_verified, + created_at, + updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) -RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at; - +RETURNING id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at; -- name: GetUserByID :one SELECT * FROM users WHERE id = $1; - -- name: GetAllUsers :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users; - -- name: SearchUserByNameOrPhone :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users -WHERE first_name ILIKE '%' || $1 || '%' - OR last_name ILIKE '%' || $1 || '%' +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' OR phone_number LIKE '%' || $1 || '%'; - -- name: UpdateUser :exec UPDATE users -SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = $6 +SET first_name = $1, + last_name = $2, + email = $3, + phone_number = $4, + role = $5, + updated_at = $6 WHERE id = $7; - -- name: DeleteUser :exec DELETE FROM users WHERE id = $1; - -- name: CheckPhoneEmailExist :one -SELECT - EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists, - EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists; +SELECT EXISTS ( + SELECT 1 + FROM users + WHERE users.phone_number = $1 + AND users.phone_number IS NOT NULL + ) AS phone_exists, + EXISTS ( + SELECT 1 + FROM users + WHERE users.email = $2 + AND users.email IS NOT NULL + ) AS email_exists; -- name: GetUserByEmail :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE email = $1; - -- name: GetUserByPhone :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE phone_number = $1; - -- name: UpdatePassword :exec UPDATE users -SET password = $1, updated_at = $4 -WHERE (email = $2 OR phone_number = $3); \ No newline at end of file +SET password = $1, + updated_at = $4 +WHERE ( + email = $2 + OR phone_number = $3 + ); \ No newline at end of file diff --git a/db/query/wallet.sql b/db/query/wallet.sql index dc025e9..0f4d27d 100644 --- a/db/query/wallet.sql +++ b/db/query/wallet.sql @@ -1,21 +1,34 @@ -- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, is_transferable, user_id) VALUES ($1, $2, $3, $4) RETURNING *; - +INSERT INTO wallets ( + is_withdraw, + is_bettable, + is_transferable, + user_id + ) +VALUES ($1, $2, $3, $4) +RETURNING *; -- name: CreateCustomerWallet :one -INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING *; - +INSERT INTO customer_wallets ( + customer_id, + company_id, + regular_wallet_id, + static_wallet_id + ) +VALUES ($1, $2, $3, $4) +RETURNING *; -- name: GetAllWallets :many -SELECT * FROM wallets; - +SELECT * +FROM wallets; -- name: GetWalletByID :one -SELECT * FROM wallets WHERE id = $1; - +SELECT * +FROM wallets +WHERE id = $1; -- name: GetWalletByUserID :many -SELECT * FROM wallets WHERE user_id = $1; - +SELECT * +FROM wallets +WHERE user_id = $1; -- name: GetCustomerWallet :one -SELECT - cw.id, +SELECT cw.id, cw.customer_id, cw.company_id, rw.id AS regular_id, @@ -26,15 +39,30 @@ SELECT sw.updated_at as static_updated_at, cw.created_at FROM customer_wallets cw -JOIN wallets rw ON cw.regular_wallet_id = rw.id -JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1 AND cw.company_id = $2; - + JOIN wallets rw ON cw.regular_wallet_id = rw.id + JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 + AND cw.company_id = $2; +-- name: GetAllBranchWallets :many +SELECT wallets.id, + wallets.balance, + wallets.is_active, + wallets.updated_at, + wallets.created_at, + branches.name, + branches.location, + branches.branch_manager_id, + branches.company_id, + branches.is_self_owned +FROM branches + JOIN wallets ON branches.wallet_id = wallets.id; -- name: UpdateBalance :exec -UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; - +UPDATE wallets +SET balance = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2; -- name: UpdateWalletActive :exec -UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; - - - +UPDATE wallets +SET is_active = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2; \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index bebc6db..71ba3a4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -618,6 +618,44 @@ const docTemplate = `{ } } }, + "/branch/{id}/bets": { + "get": { + "description": "Gets bets by its branch id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets bets by its branch id", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BetRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/branch/{id}/operation": { "get": { "description": "Gets branch operations", @@ -716,6 +754,44 @@ const docTemplate = `{ } } }, + "/branchWallet": { + "get": { + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all branch wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/cashiers": { "get": { "description": "Get all cashiers", @@ -1170,6 +1246,53 @@ const docTemplate = `{ } } }, + "/search/branch": { + "get": { + "description": "Search branches by name or location", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Search branches", + "parameters": [ + { + "type": "string", + "description": "Search query", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/supportedOperation": { "get": { "description": "Gets all supported operations", @@ -1555,7 +1678,53 @@ const docTemplate = `{ } } }, - "/transfer/wallet": { + "/transfer/refill/:id": { + "post": { + "description": "Super Admin route to refill a wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Refill wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "refillWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/:id": { "post": { "description": "Create a transfer to wallet", "consumes": [ @@ -1583,7 +1752,53 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.TransferRes" + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/{id}": { + "get": { + "description": "Get transfer by wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Get transfer by wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" } }, "400": { @@ -2101,6 +2316,23 @@ const docTemplate = `{ } }, "definitions": { + "domain.BetOutcome": { + "type": "object", + "properties": { + "betID": { + "type": "integer" + }, + "eventID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "oddID": { + "type": "integer" + } + } + }, "domain.BetStatus": { "type": "integer", "enum": [ @@ -2116,9 +2348,6 @@ const docTemplate = `{ "BET_STATUS_ERROR" ] }, - "domain.Outcome": { - "type": "object" - }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -2151,6 +2380,36 @@ const docTemplate = `{ "RoleCashier" ] }, + "domain.TicketOutcome": { + "type": "object", + "properties": { + "eventID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "oddID": { + "type": "integer" + }, + "ticketID": { + "type": "integer" + } + } + }, + "handlers.BetOutcome": { + "type": "object", + "properties": { + "event_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.BetRes": { "type": "object", "properties": { @@ -2177,7 +2436,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.BetOutcome" } }, "phone_number": { @@ -2213,6 +2472,10 @@ const docTemplate = `{ "type": "integer", "example": 1 }, + "id": { + "type": "integer", + "example": 1 + }, "is_self_owned": { "type": "boolean", "example": false @@ -2263,6 +2526,10 @@ const docTemplate = `{ "type": "integer", "example": 1 }, + "id": { + "type": "integer", + "example": 1 + }, "is_self_owned": { "type": "boolean", "example": false @@ -2306,43 +2573,7 @@ const docTemplate = `{ } }, "handlers.CreateBetReq": { - "type": "object", - "properties": { - "amount": { - "type": "number", - "example": 100 - }, - "full_name": { - "type": "string", - "example": "John" - }, - "is_shop_bet": { - "type": "boolean", - "example": false - }, - "outcomes": { - "type": "array", - "items": { - "type": "integer" - } - }, - "phone_number": { - "type": "string", - "example": "1234567890" - }, - "status": { - "allOf": [ - { - "$ref": "#/definitions/domain.BetStatus" - } - ], - "example": 1 - }, - "total_odds": { - "type": "number", - "example": 4.22 - } - } + "type": "object" }, "handlers.CreateBranchOperationReq": { "type": "object", @@ -2391,6 +2622,10 @@ const docTemplate = `{ "handlers.CreateCashierReq": { "type": "object", "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, "email": { "type": "string", "example": "john.doe@example.com" @@ -2416,10 +2651,6 @@ const docTemplate = `{ "handlers.CreateManagerReq": { "type": "object", "properties": { - "branch_id": { - "type": "integer", - "example": 1 - }, "email": { "type": "string", "example": "john.doe@example.com" @@ -2465,7 +2696,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.TicketOutcome" } }, "total_odds": { @@ -2477,6 +2708,10 @@ const docTemplate = `{ "handlers.CreateTicketRes": { "type": "object", "properties": { + "created_number": { + "type": "integer", + "example": 3 + }, "fast_code": { "type": "integer", "example": 1234 @@ -2550,10 +2785,6 @@ const docTemplate = `{ "payment_method": { "type": "string", "example": "cash" - }, - "receiver_id": { - "type": "integer", - "example": 1 } } }, @@ -2599,6 +2830,17 @@ const docTemplate = `{ } } }, + "handlers.NullableInt64": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "value": { + "type": "integer" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { @@ -2701,6 +2943,19 @@ const docTemplate = `{ } } }, + "handlers.TicketOutcome": { + "type": "object", + "properties": { + "event_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -2715,7 +2970,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.TicketOutcome" } }, "total_odds": { @@ -2788,7 +3043,7 @@ const docTemplate = `{ } } }, - "handlers.TransferRes": { + "handlers.TransferWalletRes": { "type": "object", "properties": { "amount": { diff --git a/docs/swagger.json b/docs/swagger.json index d6e6201..5e4b713 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -610,6 +610,44 @@ } } }, + "/branch/{id}/bets": { + "get": { + "description": "Gets bets by its branch id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets bets by its branch id", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BetRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/branch/{id}/operation": { "get": { "description": "Gets branch operations", @@ -708,6 +746,44 @@ } } }, + "/branchWallet": { + "get": { + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all branch wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/cashiers": { "get": { "description": "Get all cashiers", @@ -1162,6 +1238,53 @@ } } }, + "/search/branch": { + "get": { + "description": "Search branches by name or location", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Search branches", + "parameters": [ + { + "type": "string", + "description": "Search query", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/supportedOperation": { "get": { "description": "Gets all supported operations", @@ -1547,7 +1670,53 @@ } } }, - "/transfer/wallet": { + "/transfer/refill/:id": { + "post": { + "description": "Super Admin route to refill a wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Refill wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "refillWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/:id": { "post": { "description": "Create a transfer to wallet", "consumes": [ @@ -1575,7 +1744,53 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.TransferRes" + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/{id}": { + "get": { + "description": "Get transfer by wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Get transfer by wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" } }, "400": { @@ -2093,6 +2308,23 @@ } }, "definitions": { + "domain.BetOutcome": { + "type": "object", + "properties": { + "betID": { + "type": "integer" + }, + "eventID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "oddID": { + "type": "integer" + } + } + }, "domain.BetStatus": { "type": "integer", "enum": [ @@ -2108,9 +2340,6 @@ "BET_STATUS_ERROR" ] }, - "domain.Outcome": { - "type": "object" - }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -2143,6 +2372,36 @@ "RoleCashier" ] }, + "domain.TicketOutcome": { + "type": "object", + "properties": { + "eventID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "oddID": { + "type": "integer" + }, + "ticketID": { + "type": "integer" + } + } + }, + "handlers.BetOutcome": { + "type": "object", + "properties": { + "event_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.BetRes": { "type": "object", "properties": { @@ -2169,7 +2428,7 @@ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.BetOutcome" } }, "phone_number": { @@ -2205,6 +2464,10 @@ "type": "integer", "example": 1 }, + "id": { + "type": "integer", + "example": 1 + }, "is_self_owned": { "type": "boolean", "example": false @@ -2255,6 +2518,10 @@ "type": "integer", "example": 1 }, + "id": { + "type": "integer", + "example": 1 + }, "is_self_owned": { "type": "boolean", "example": false @@ -2298,43 +2565,7 @@ } }, "handlers.CreateBetReq": { - "type": "object", - "properties": { - "amount": { - "type": "number", - "example": 100 - }, - "full_name": { - "type": "string", - "example": "John" - }, - "is_shop_bet": { - "type": "boolean", - "example": false - }, - "outcomes": { - "type": "array", - "items": { - "type": "integer" - } - }, - "phone_number": { - "type": "string", - "example": "1234567890" - }, - "status": { - "allOf": [ - { - "$ref": "#/definitions/domain.BetStatus" - } - ], - "example": 1 - }, - "total_odds": { - "type": "number", - "example": 4.22 - } - } + "type": "object" }, "handlers.CreateBranchOperationReq": { "type": "object", @@ -2383,6 +2614,10 @@ "handlers.CreateCashierReq": { "type": "object", "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, "email": { "type": "string", "example": "john.doe@example.com" @@ -2408,10 +2643,6 @@ "handlers.CreateManagerReq": { "type": "object", "properties": { - "branch_id": { - "type": "integer", - "example": 1 - }, "email": { "type": "string", "example": "john.doe@example.com" @@ -2457,7 +2688,7 @@ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.TicketOutcome" } }, "total_odds": { @@ -2469,6 +2700,10 @@ "handlers.CreateTicketRes": { "type": "object", "properties": { + "created_number": { + "type": "integer", + "example": 3 + }, "fast_code": { "type": "integer", "example": 1234 @@ -2542,10 +2777,6 @@ "payment_method": { "type": "string", "example": "cash" - }, - "receiver_id": { - "type": "integer", - "example": 1 } } }, @@ -2591,6 +2822,17 @@ } } }, + "handlers.NullableInt64": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "value": { + "type": "integer" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { @@ -2693,6 +2935,19 @@ } } }, + "handlers.TicketOutcome": { + "type": "object", + "properties": { + "event_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -2707,7 +2962,7 @@ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.TicketOutcome" } }, "total_odds": { @@ -2780,7 +3035,7 @@ } } }, - "handlers.TransferRes": { + "handlers.TransferWalletRes": { "type": "object", "properties": { "amount": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7345a67..604daf7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,4 +1,15 @@ definitions: + domain.BetOutcome: + properties: + betID: + type: integer + eventID: + type: integer + id: + type: integer + oddID: + type: integer + type: object domain.BetStatus: enum: - 0 @@ -11,8 +22,6 @@ definitions: - BET_STATUS_WIN - BET_STATUS_LOSS - BET_STATUS_ERROR - domain.Outcome: - type: object domain.PaymentOption: enum: - 0 @@ -39,6 +48,26 @@ definitions: - RoleBranchManager - RoleCustomer - RoleCashier + domain.TicketOutcome: + properties: + eventID: + type: integer + id: + type: integer + oddID: + type: integer + ticketID: + type: integer + type: object + handlers.BetOutcome: + properties: + event_id: + example: 1 + type: integer + odd_id: + example: 1 + type: integer + type: object handlers.BetRes: properties: amount: @@ -58,7 +87,7 @@ definitions: type: boolean outcomes: items: - $ref: '#/definitions/domain.Outcome' + $ref: '#/definitions/domain.BetOutcome' type: array phone_number: example: "1234567890" @@ -82,6 +111,9 @@ definitions: company_id: example: 1 type: integer + id: + example: 1 + type: integer is_self_owned: example: false type: boolean @@ -118,6 +150,9 @@ definitions: company_id: example: 1 type: integer + id: + example: 1 + type: integer is_self_owned: example: false type: boolean @@ -148,30 +183,6 @@ definitions: type: boolean type: object handlers.CreateBetReq: - properties: - amount: - example: 100 - type: number - full_name: - example: John - type: string - is_shop_bet: - example: false - type: boolean - outcomes: - items: - type: integer - type: array - phone_number: - example: "1234567890" - type: string - status: - allOf: - - $ref: '#/definitions/domain.BetStatus' - example: 1 - total_odds: - example: 4.22 - type: number type: object handlers.CreateBranchOperationReq: properties: @@ -206,6 +217,9 @@ definitions: type: object handlers.CreateCashierReq: properties: + branch_id: + example: 1 + type: integer email: example: john.doe@example.com type: string @@ -224,9 +238,6 @@ definitions: type: object handlers.CreateManagerReq: properties: - branch_id: - example: 1 - type: integer email: example: john.doe@example.com type: string @@ -259,7 +270,7 @@ definitions: type: number outcomes: items: - type: integer + $ref: '#/definitions/handlers.TicketOutcome' type: array total_odds: example: 4.22 @@ -267,6 +278,9 @@ definitions: type: object handlers.CreateTicketRes: properties: + created_number: + example: 3 + type: integer fast_code: example: 1234 type: integer @@ -318,9 +332,6 @@ definitions: payment_method: example: cash type: string - receiver_id: - example: 1 - type: integer type: object handlers.CustomerWalletRes: properties: @@ -352,6 +363,13 @@ definitions: static_updated_at: type: string type: object + handlers.NullableInt64: + properties: + valid: + type: boolean + value: + type: integer + type: object handlers.RegisterCodeReq: properties: email: @@ -423,6 +441,15 @@ definitions: example: SportsBook type: string type: object + handlers.TicketOutcome: + properties: + event_id: + example: 1 + type: integer + odd_id: + example: 1 + type: integer + type: object handlers.TicketRes: properties: amount: @@ -433,7 +460,7 @@ definitions: type: integer outcomes: items: - $ref: '#/definitions/domain.Outcome' + $ref: '#/definitions/domain.TicketOutcome' type: array total_odds: example: 4.22 @@ -483,7 +510,7 @@ definitions: example: true type: boolean type: object - handlers.TransferRes: + handlers.TransferWalletRes: properties: amount: example: 100 @@ -1055,6 +1082,31 @@ paths: summary: Updates a branch tags: - branch + /branch/{id}/bets: + get: + consumes: + - application/json + description: Gets bets by its branch id + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BetRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets bets by its branch id + tags: + - branch /branch/{id}/operation: get: consumes: @@ -1120,6 +1172,31 @@ paths: summary: Delete the branch operation tags: - branch + /branchWallet: + get: + consumes: + - application/json + description: Retrieve all branch wallets + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.WalletRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all branch wallets + tags: + - wallet /cashiers: get: consumes: @@ -1418,6 +1495,37 @@ paths: summary: Create a operation tags: - branch + /search/branch: + get: + consumes: + - application/json + description: Search branches by name or location + parameters: + - description: Search query + in: query + name: q + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Search branches + tags: + - branch /supportedOperation: get: consumes: @@ -1672,7 +1780,37 @@ paths: summary: Updates the cashed out field tags: - transaction - /transfer/wallet: + /transfer/refill/:id: + post: + consumes: + - application/json + description: Super Admin route to refill a wallet + parameters: + - description: Create Transfer + in: body + name: refillWallet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransferReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransferWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Refill wallet + tags: + - transfer + /transfer/wallet/:id: post: consumes: - application/json @@ -1690,7 +1828,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handlers.TransferRes' + $ref: '#/definitions/handlers.TransferWalletRes' "400": description: Bad Request schema: @@ -1702,6 +1840,36 @@ paths: summary: Create a transfer to wallet tags: - transfer + /transfer/wallet/{id}: + get: + consumes: + - application/json + description: Get transfer by wallet + parameters: + - description: Create Transfer + in: body + name: transferToWallet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransferReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransferWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get transfer by wallet + tags: + - transfer /user/checkPhoneEmailExist: post: consumes: diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 79b5cf3..89a636a 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -12,7 +12,17 @@ import ( ) const CreateBet = `-- name: CreateBet :one -INSERT INTO bets (amount, total_odds, status, full_name, phone_number, branch_id, user_id, is_shop_bet, cashout_id) +INSERT INTO bets ( + amount, + total_odds, + status, + full_name, + phone_number, + branch_id, + user_id, + is_shop_bet, + cashout_id + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet ` @@ -60,8 +70,15 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro return i, err } +type CreateBetOutcomeParams struct { + BetID int64 + EventID int64 + OddID int64 +} + const DeleteBet = `-- name: DeleteBet :exec -DELETE FROM bets WHERE id = $1 +DELETE FROM bets +WHERE id = $1 ` func (q *Queries) DeleteBet(ctx context.Context, id int64) error { @@ -69,19 +86,30 @@ func (q *Queries) DeleteBet(ctx context.Context, id int64) error { return err } -const GetAllBets = `-- name: GetAllBets :many -SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet FROM bets +const DeleteBetOutcome = `-- name: DeleteBetOutcome :exec +DELETE FROM bet_outcomes +WHERE bet_id = $1 ` -func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { +func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error { + _, err := q.db.Exec(ctx, DeleteBetOutcome, betID) + return err +} + +const GetAllBets = `-- name: GetAllBets :many +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +` + +func (q *Queries) GetAllBets(ctx context.Context) ([]BetWithOutcome, error) { rows, err := q.db.Query(ctx, GetAllBets) if err != nil { return nil, err } defer rows.Close() - var items []Bet + var items []BetWithOutcome for rows.Next() { - var i Bet + var i BetWithOutcome if err := rows.Scan( &i.ID, &i.Amount, @@ -96,6 +124,7 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.Outcomes, ); err != nil { return nil, err } @@ -107,13 +136,85 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { return items, nil } -const GetBetByID = `-- name: GetBetByID :one -SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet FROM bets WHERE id = $1 +const GetBetByBranchID = `-- name: GetBetByBranchID :many +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE branch_id = $1 ` -func (q *Queries) GetBetByID(ctx context.Context, id int64) (Bet, error) { +func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([]BetWithOutcome, error) { + rows, err := q.db.Query(ctx, GetBetByBranchID, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BetWithOutcome + for rows.Next() { + var i BetWithOutcome + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.TotalOdds, + &i.Status, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.UserID, + &i.CashedOut, + &i.CashoutID, + &i.CreatedAt, + &i.UpdatedAt, + &i.IsShopBet, + &i.Outcomes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBetByCashoutID = `-- name: GetBetByCashoutID :many +SELECT +FROM bet_with_outcomes +WHERE cashout_id = $1 +` + +type GetBetByCashoutIDRow struct { +} + +func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) ([]GetBetByCashoutIDRow, error) { + rows, err := q.db.Query(ctx, GetBetByCashoutID, cashoutID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetBetByCashoutIDRow + for rows.Next() { + var i GetBetByCashoutIDRow + if err := rows.Scan(); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBetByID = `-- name: GetBetByID :one +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE id = $1 +` + +func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, error) { row := q.db.QueryRow(ctx, GetBetByID, id) - var i Bet + var i BetWithOutcome err := row.Scan( &i.ID, &i.Amount, @@ -128,12 +229,16 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (Bet, error) { &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.Outcomes, ) return i, err } const UpdateCashOut = `-- name: UpdateCashOut :exec -UPDATE bets SET cashed_out = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1 +UPDATE bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 ` type UpdateCashOutParams struct { diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index 8d34fb3..d1d8e99 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -12,7 +12,16 @@ import ( ) const CreateBranch = `-- name: CreateBranch :one -INSERT INTO branches (name, location, wallet_id, branch_manager_id, company_id, is_self_owned) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at +INSERT INTO branches ( + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at ` type CreateBranchParams struct { @@ -48,8 +57,28 @@ func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Bra return i, err } +const CreateBranchCashier = `-- name: CreateBranchCashier :one +INSERT INTO branch_cashiers (user_id, branch_id) +VALUES ($1, $2) +RETURNING id, user_id, branch_id +` + +type CreateBranchCashierParams struct { + UserID int64 + BranchID int64 +} + +func (q *Queries) CreateBranchCashier(ctx context.Context, arg CreateBranchCashierParams) (BranchCashier, error) { + row := q.db.QueryRow(ctx, CreateBranchCashier, arg.UserID, arg.BranchID) + var i BranchCashier + err := row.Scan(&i.ID, &i.UserID, &i.BranchID) + return i, err +} + const CreateBranchOperation = `-- name: CreateBranchOperation :one -INSERT INTO branch_operations (operation_id, branch_id) VALUES ($1, $2) RETURNING id, operation_id, branch_id, created_at, updated_at +INSERT INTO branch_operations (operation_id, branch_id) +VALUES ($1, $2) +RETURNING id, operation_id, branch_id, created_at, updated_at ` type CreateBranchOperationParams struct { @@ -71,7 +100,9 @@ func (q *Queries) CreateBranchOperation(ctx context.Context, arg CreateBranchOpe } const CreateSupportedOperation = `-- name: CreateSupportedOperation :one -INSERT INTO supported_operations (name, description) VALUES ($1, $2) RETURNING id, name, description +INSERT INTO supported_operations (name, description) +VALUES ($1, $2) +RETURNING id, name, description ` type CreateSupportedOperationParams struct { @@ -87,7 +118,8 @@ func (q *Queries) CreateSupportedOperation(ctx context.Context, arg CreateSuppor } const DeleteBranch = `-- name: DeleteBranch :exec -DELETE FROM branches WHERE id = $1 +DELETE FROM branches +WHERE id = $1 ` func (q *Queries) DeleteBranch(ctx context.Context, id int64) error { @@ -95,8 +127,20 @@ func (q *Queries) DeleteBranch(ctx context.Context, id int64) error { return err } +const DeleteBranchCashier = `-- name: DeleteBranchCashier :exec +DELETE FROM branch_cashiers +WHERE user_id = $1 +` + +func (q *Queries) DeleteBranchCashier(ctx context.Context, userID int64) error { + _, err := q.db.Exec(ctx, DeleteBranchCashier, userID) + return err +} + const DeleteBranchOperation = `-- name: DeleteBranchOperation :exec -DELETE FROM branch_operations WHERE operation_id = $1 AND branch_id = $2 +DELETE FROM branch_operations +WHERE operation_id = $1 + AND branch_id = $2 ` type DeleteBranchOperationParams struct { @@ -110,7 +154,8 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe } const GetAllBranches = `-- name: GetAllBranches :many -SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details ` func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) { @@ -145,8 +190,49 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) { return items, nil } +const GetAllCashiers = `-- name: GetAllCashiers :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +` + +func (q *Queries) GetAllCashiers(ctx context.Context) ([]User, error) { + rows, err := q.db.Query(ctx, GetAllCashiers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.SuspendedAt, + &i.Suspended, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAllSupportedOperations = `-- name: GetAllSupportedOperations :many -SELECT id, name, description FROM supported_operations +SELECT id, name, description +FROM supported_operations ` func (q *Queries) GetAllSupportedOperations(ctx context.Context) ([]SupportedOperation, error) { @@ -169,8 +255,34 @@ func (q *Queries) GetAllSupportedOperations(ctx context.Context) ([]SupportedOpe return items, nil } +const GetBranchByCashier = `-- name: GetBranchByCashier :one +SELECT branches.id, branches.name, branches.location, branches.wallet_id, branches.branch_manager_id, branches.company_id, branches.is_self_owned, branches.created_at, branches.updated_at +FROM branch_cashiers + JOIN branches ON branch_cashiers.branch_id = branches.id +WHERE branch_cashiers.user_id = $1 +` + +func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch, error) { + row := q.db.QueryRow(ctx, GetBranchByCashier, userID) + var i Branch + err := row.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many -SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details WHERE company_id = $1 +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE company_id = $1 ` func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]BranchDetail, error) { @@ -206,7 +318,9 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([] } const GetBranchByID = `-- name: GetBranchByID :one -SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details WHERE id = $1 +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE id = $1 ` func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, error) { @@ -229,7 +343,9 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er } const GetBranchByManagerID = `-- name: GetBranchByManagerID :many -SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details WHERE branch_manager_id = $1 +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE branch_manager_id = $1 ` func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]BranchDetail, error) { @@ -265,9 +381,11 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6 } const GetBranchOperations = `-- name: GetBranchOperations :many -SELECT branch_operations.id, branch_operations.operation_id, branch_operations.branch_id, branch_operations.created_at, branch_operations.updated_at, supported_operations.name, supported_operations.description -FROM branch_operations -JOIN supported_operations ON branch_operations.operation_id = supported_operations.id +SELECT branch_operations.id, branch_operations.operation_id, branch_operations.branch_id, branch_operations.created_at, branch_operations.updated_at, + supported_operations.name, + supported_operations.description +FROM branch_operations + JOIN supported_operations ON branch_operations.operation_id = supported_operations.id WHERE branch_operations.branch_id = $1 ` @@ -309,8 +427,51 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge return items, nil } +const GetCashiersByBranch = `-- name: GetCashiersByBranch :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1 +` + +func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]User, error) { + rows, err := q.db.Query(ctx, GetCashiersByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.SuspendedAt, + &i.Suspended, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const SearchBranchByName = `-- name: SearchBranchByName :many -SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details WHERE name ILIKE '%' || $1 || '%' +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE name ILIKE '%' || $1 || '%' ` func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) ([]BranchDetail, error) { @@ -346,7 +507,14 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) } const UpdateBranch = `-- name: UpdateBranch :one -UPDATE branches SET name = $1, location = $2, branch_manager_id = $3, company_id = $4, is_self_owned = $5 WHERE id = $6 RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at +UPDATE branches +SET name = $1, + location = $2, + branch_manager_id = $3, + company_id = $4, + is_self_owned = $5 +WHERE id = $6 +RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at ` type UpdateBranchParams struct { diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go new file mode 100644 index 0000000..f3dffed --- /dev/null +++ b/gen/db/copyfrom.go @@ -0,0 +1,78 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: copyfrom.go + +package dbgen + +import ( + "context" +) + +// iteratorForCreateBetOutcome implements pgx.CopyFromSource. +type iteratorForCreateBetOutcome struct { + rows []CreateBetOutcomeParams + skippedFirstNextCall bool +} + +func (r *iteratorForCreateBetOutcome) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].BetID, + r.rows[0].EventID, + r.rows[0].OddID, + }, nil +} + +func (r iteratorForCreateBetOutcome) Err() error { + return nil +} + +func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) { + return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"bet_id", "event_id", "odd_id"}, &iteratorForCreateBetOutcome{rows: arg}) +} + +// iteratorForCreateTicketOutcome implements pgx.CopyFromSource. +type iteratorForCreateTicketOutcome struct { + rows []CreateTicketOutcomeParams + skippedFirstNextCall bool +} + +func (r *iteratorForCreateTicketOutcome) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForCreateTicketOutcome) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].TicketID, + r.rows[0].EventID, + r.rows[0].OddID, + }, nil +} + +func (r iteratorForCreateTicketOutcome) Err() error { + return nil +} + +func (q *Queries) CreateTicketOutcome(ctx context.Context, arg []CreateTicketOutcomeParams) (int64, error) { + return q.db.CopyFrom(ctx, []string{"ticket_outcomes"}, []string{"ticket_id", "event_id", "odd_id"}, &iteratorForCreateTicketOutcome{rows: arg}) +} diff --git a/gen/db/db.go b/gen/db/db.go index 136f20a..d892683 100644 --- a/gen/db/db.go +++ b/gen/db/db.go @@ -15,6 +15,7 @@ type DBTX interface { Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) Query(context.Context, string, ...interface{}) (pgx.Rows, error) QueryRow(context.Context, string, ...interface{}) pgx.Row + CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) } func New(db DBTX) *Queries { diff --git a/gen/db/models.go b/gen/db/models.go index e96ee34..d41d796 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -24,6 +24,30 @@ type Bet struct { IsShopBet bool } +type BetOutcome struct { + ID int64 + BetID int64 + EventID int64 + OddID int64 +} + +type BetWithOutcome struct { + ID int64 + Amount int64 + TotalOdds float32 + Status int32 + FullName string + PhoneNumber string + BranchID pgtype.Int8 + UserID pgtype.Int8 + CashedOut bool + CashoutID string + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp + IsShopBet bool + Outcomes []BetOutcome +} + type Branch struct { ID int64 Name string @@ -36,6 +60,12 @@ type Branch struct { UpdatedAt pgtype.Timestamp } +type BranchCashier struct { + ID int64 + UserID int64 + BranchID int64 +} + type BranchDetail struct { ID int64 Name string @@ -120,6 +150,22 @@ type Ticket struct { UpdatedAt pgtype.Timestamp } +type TicketOutcome struct { + ID int64 + TicketID int64 + EventID int64 + OddID int64 +} + +type TicketWithOutcome struct { + ID int64 + Amount pgtype.Int8 + TotalOdds float32 + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp + Outcomes []TicketOutcome +} + type Transaction struct { ID int64 Amount int64 diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index d7e5ff3..150d386 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -35,8 +35,15 @@ func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Tic return i, err } +type CreateTicketOutcomeParams struct { + TicketID int64 + EventID int64 + OddID int64 +} + const DeleteOldTickets = `-- name: DeleteOldTickets :exec -Delete from tickets where created_at < now() - interval '1 day' +Delete from tickets +where created_at < now() - interval '1 day' ` func (q *Queries) DeleteOldTickets(ctx context.Context) error { @@ -45,7 +52,8 @@ func (q *Queries) DeleteOldTickets(ctx context.Context) error { } const DeleteTicket = `-- name: DeleteTicket :exec -DELETE FROM tickets WHERE id = $1 +DELETE FROM tickets +WHERE id = $1 ` func (q *Queries) DeleteTicket(ctx context.Context, id int64) error { @@ -53,25 +61,37 @@ func (q *Queries) DeleteTicket(ctx context.Context, id int64) error { return err } -const GetAllTickets = `-- name: GetAllTickets :many -SELECT id, amount, total_odds, created_at, updated_at FROM tickets +const DeleteTicketOutcome = `-- name: DeleteTicketOutcome :exec +Delete from ticket_outcomes +where ticket_id = $1 ` -func (q *Queries) GetAllTickets(ctx context.Context) ([]Ticket, error) { +func (q *Queries) DeleteTicketOutcome(ctx context.Context, ticketID int64) error { + _, err := q.db.Exec(ctx, DeleteTicketOutcome, ticketID) + return err +} + +const GetAllTickets = `-- name: GetAllTickets :many +SELECT id, amount, total_odds, created_at, updated_at, outcomes +FROM ticket_with_outcomes +` + +func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error) { rows, err := q.db.Query(ctx, GetAllTickets) if err != nil { return nil, err } defer rows.Close() - var items []Ticket + var items []TicketWithOutcome for rows.Next() { - var i Ticket + var i TicketWithOutcome if err := rows.Scan( &i.ID, &i.Amount, &i.TotalOdds, &i.CreatedAt, &i.UpdatedAt, + &i.Outcomes, ); err != nil { return nil, err } @@ -84,18 +104,52 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]Ticket, error) { } const GetTicketByID = `-- name: GetTicketByID :one -SELECT id, amount, total_odds, created_at, updated_at FROM tickets WHERE id = $1 +SELECT id, amount, total_odds, created_at, updated_at, outcomes +FROM ticket_with_outcomes +WHERE id = $1 ` -func (q *Queries) GetTicketByID(ctx context.Context, id int64) (Ticket, error) { +func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcome, error) { row := q.db.QueryRow(ctx, GetTicketByID, id) - var i Ticket + var i TicketWithOutcome err := row.Scan( &i.ID, &i.Amount, &i.TotalOdds, &i.CreatedAt, &i.UpdatedAt, + &i.Outcomes, ) return i, err } + +const GetTicketOutcome = `-- name: GetTicketOutcome :many +SELECT id, ticket_id, event_id, odd_id +FROM ticket_outcomes +WHERE ticket_id = $1 +` + +func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]TicketOutcome, error) { + rows, err := q.db.Query(ctx, GetTicketOutcome, ticketID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TicketOutcome + for rows.Next() { + var i TicketOutcome + if err := rows.Scan( + &i.ID, + &i.TicketID, + &i.EventID, + &i.OddID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index bce776c..e259cb9 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -12,9 +12,18 @@ import ( ) const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one -SELECT - EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists, - EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists +SELECT EXISTS ( + SELECT 1 + FROM users + WHERE users.phone_number = $1 + AND users.phone_number IS NOT NULL + ) AS phone_exists, + EXISTS ( + SELECT 1 + FROM users + WHERE users.email = $2 + AND users.email IS NOT NULL + ) AS email_exists ` type CheckPhoneEmailExistParams struct { @@ -35,10 +44,29 @@ func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailE } const CreateUser = `-- name: CreateUser :one - -INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at) +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + role, + password, + email_verified, + phone_verified, + created_at, + updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) -RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +RETURNING id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at ` type CreateUserParams struct { @@ -107,7 +135,16 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error { } const GetAllUsers = `-- name: GetAllUsers :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users ` @@ -156,7 +193,16 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) { } const GetUserByEmail = `-- name: GetUserByEmail :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE email = $1 ` @@ -220,7 +266,16 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { } const GetUserByPhone = `-- name: GetUserByPhone :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE phone_number = $1 ` @@ -257,10 +312,19 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) ( } const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users -WHERE first_name ILIKE '%' || $1 || '%' - OR last_name ILIKE '%' || $1 || '%' +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' OR phone_number LIKE '%' || $1 || '%' ` @@ -310,8 +374,12 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.T const UpdatePassword = `-- name: UpdatePassword :exec UPDATE users -SET password = $1, updated_at = $4 -WHERE (email = $2 OR phone_number = $3) +SET password = $1, + updated_at = $4 +WHERE ( + email = $2 + OR phone_number = $3 + ) ` type UpdatePasswordParams struct { @@ -333,7 +401,12 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) const UpdateUser = `-- name: UpdateUser :exec UPDATE users -SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = $6 +SET first_name = $1, + last_name = $2, + email = $3, + phone_number = $4, + role = $5, + updated_at = $6 WHERE id = $7 ` diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 0700a07..5c3410a 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -12,7 +12,14 @@ import ( ) const CreateCustomerWallet = `-- name: CreateCustomerWallet :one -INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING id, customer_id, company_id, regular_wallet_id, static_wallet_id, created_at, updated_at +INSERT INTO customer_wallets ( + customer_id, + company_id, + regular_wallet_id, + static_wallet_id + ) +VALUES ($1, $2, $3, $4) +RETURNING id, customer_id, company_id, regular_wallet_id, static_wallet_id, created_at, updated_at ` type CreateCustomerWalletParams struct { @@ -43,7 +50,14 @@ func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWa } const CreateWallet = `-- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, is_transferable, user_id) VALUES ($1, $2, $3, $4) RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at +INSERT INTO wallets ( + is_withdraw, + is_bettable, + is_transferable, + user_id + ) +VALUES ($1, $2, $3, $4) +RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at ` type CreateWalletParams struct { @@ -75,8 +89,68 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal return i, err } +const GetAllBranchWallets = `-- name: GetAllBranchWallets :many +SELECT wallets.id, + wallets.balance, + wallets.is_active, + wallets.updated_at, + wallets.created_at, + branches.name, + branches.location, + branches.branch_manager_id, + branches.company_id, + branches.is_self_owned +FROM branches + JOIN wallets ON branches.wallet_id = wallets.id +` + +type GetAllBranchWalletsRow struct { + ID int64 + Balance int64 + IsActive bool + UpdatedAt pgtype.Timestamp + CreatedAt pgtype.Timestamp + Name string + Location string + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool +} + +func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWalletsRow, error) { + rows, err := q.db.Query(ctx, GetAllBranchWallets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllBranchWalletsRow + for rows.Next() { + var i GetAllBranchWalletsRow + if err := rows.Scan( + &i.ID, + &i.Balance, + &i.IsActive, + &i.UpdatedAt, + &i.CreatedAt, + &i.Name, + &i.Location, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAllWallets = `-- name: GetAllWallets :many -SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at FROM wallets +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at +FROM wallets ` func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { @@ -110,8 +184,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { } const GetCustomerWallet = `-- name: GetCustomerWallet :one -SELECT - cw.id, +SELECT cw.id, cw.customer_id, cw.company_id, rw.id AS regular_id, @@ -122,9 +195,10 @@ SELECT sw.updated_at as static_updated_at, cw.created_at FROM customer_wallets cw -JOIN wallets rw ON cw.regular_wallet_id = rw.id -JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1 AND cw.company_id = $2 + JOIN wallets rw ON cw.regular_wallet_id = rw.id + JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 + AND cw.company_id = $2 ` type GetCustomerWalletParams struct { @@ -164,7 +238,9 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletPa } const GetWalletByID = `-- name: GetWalletByID :one -SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at FROM wallets WHERE id = $1 +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at +FROM wallets +WHERE id = $1 ` func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { @@ -185,7 +261,9 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { } const GetWalletByUserID = `-- name: GetWalletByUserID :many -SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at FROM wallets WHERE user_id = $1 +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at +FROM wallets +WHERE user_id = $1 ` func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet, error) { @@ -219,7 +297,10 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet } const UpdateBalance = `-- name: UpdateBalance :exec -UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +UPDATE wallets +SET balance = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2 ` type UpdateBalanceParams struct { @@ -233,7 +314,10 @@ func (q *Queries) UpdateBalance(ctx context.Context, arg UpdateBalanceParams) er } const UpdateWalletActive = `-- name: UpdateWalletActive :exec -UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +UPDATE wallets +SET is_active = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2 ` type UpdateWalletActiveParams struct { diff --git a/internal/domain/bet.go b/internal/domain/bet.go index cc8175d..23b3ee8 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -1,5 +1,18 @@ package domain +type BetOutcome struct { + ID int64 + BetID int64 + EventID int64 + OddID int64 +} + +type CreateBetOutcome struct { + BetID int64 + EventID int64 + OddID int64 +} + type BetStatus int const ( @@ -13,7 +26,6 @@ const ( // If it is a DigitalBet then UserID will be the user and the branchID will be 0 or nil type Bet struct { ID int64 - Outcomes []Outcome Amount Currency TotalOdds float32 Status BetStatus @@ -26,8 +38,22 @@ type Bet struct { CashoutID string } +type GetBet struct { + ID int64 + Amount Currency + TotalOdds float32 + Status BetStatus + FullName string + PhoneNumber string + BranchID ValidInt64 // Can Be Nullable + UserID ValidInt64 // Can Be Nullable + IsShopBet bool + CashedOut bool + CashoutID string + Outcomes []BetOutcome +} + type CreateBet struct { - Outcomes []int64 Amount Currency TotalOdds float32 Status BetStatus diff --git a/internal/domain/common.go b/internal/domain/common.go index 985e97e..88273f3 100644 --- a/internal/domain/common.go +++ b/internal/domain/common.go @@ -37,3 +37,5 @@ func (m Currency) String() string { return fmt.Sprintf("$%.2f", x) } + + diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index b1c000f..18cbf68 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -1,15 +1,33 @@ package domain +type TicketOutcome struct { + ID int64 + TicketID int64 + EventID int64 + OddID int64 +} + +type CreateTicketOutcome struct { + TicketID int64 + EventID int64 + OddID int64 +} + // ID will serve as the fast code since this doesn't need to be secure type Ticket struct { ID int64 - Outcomes []Outcome Amount Currency TotalOdds float32 } +type GetTicket struct { + ID int64 + Amount Currency + TotalOdds float32 + Outcomes []TicketOutcome +} + type CreateTicket struct { - Outcomes []int64 Amount Currency TotalOdds float32 } diff --git a/internal/domain/user.go b/internal/domain/user.go index 23004f4..df9aaba 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -42,7 +42,6 @@ type RegisterUserReq struct { OtpMedium OtpMedium } type CreateUserReq struct { - BranchID int64 FirstName string LastName string Email string diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index efb80ab..33e9466 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -3,23 +3,23 @@ package domain import "time" type Wallet struct { - ID int64 - Balance Currency - IsWithdraw bool - IsBettable bool + ID int64 + Balance Currency + IsWithdraw bool + IsBettable bool IsTransferable bool - IsActive bool - UserID int64 - UpdatedAt time.Time - CreatedAt time.Time + IsActive bool + UserID int64 + UpdatedAt time.Time + CreatedAt time.Time } type CustomerWallet struct { - ID int64 - RegularID int64 - StaticID int64 - CustomerID int64 - CompanyID int64 + ID int64 + RegularID int64 + StaticID int64 + CustomerID int64 + CompanyID int64 } type GetCustomerWallet struct { ID int64 @@ -34,11 +34,24 @@ type GetCustomerWallet struct { CreatedAt time.Time } +type BranchWallet struct { + ID int64 + Balance Currency + IsActive bool + Name string + Location string + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool + UpdatedAt time.Time + CreatedAt time.Time +} + type CreateWallet struct { - IsWithdraw bool - IsBettable bool + IsWithdraw bool + IsBettable bool IsTransferable bool - UserID int64 + UserID int64 } type CreateCustomerWallet struct { diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 210fcbd..5467d0f 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -30,6 +30,39 @@ func convertDBBet(bet dbgen.Bet) domain.Bet { } } +func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { + var outcomes []domain.BetOutcome = make([]domain.BetOutcome, len(bet.Outcomes)) + + for _, outcome := range bet.Outcomes { + outcomes = append(outcomes, domain.BetOutcome{ + ID: outcome.ID, + BetID: outcome.BetID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) + } + return domain.GetBet{ + ID: bet.ID, + Amount: domain.Currency(bet.Amount), + TotalOdds: bet.TotalOdds, + Status: domain.BetStatus(bet.Status), + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + BranchID: domain.ValidInt64{ + Value: bet.BranchID.Int64, + Valid: bet.BranchID.Valid, + }, + UserID: domain.ValidInt64{ + Value: bet.UserID.Int64, + Valid: bet.UserID.Valid, + }, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + CashoutID: bet.CashoutID, + Outcomes: outcomes, + } +} + func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams { return dbgen.CreateBetParams{ Amount: int64(bet.Amount), @@ -51,7 +84,6 @@ func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams { } func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { - newBet, err := s.queries.CreateBet(ctx, convertCreateBet(bet)) if err != nil { return domain.Bet{}, err @@ -60,25 +92,62 @@ func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet } -func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.Bet, error) { - bet, err := s.queries.GetBetByID(ctx, id) +func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) { + var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes)) + + for _, outcome := range outcomes { + dbParams = append(dbParams, dbgen.CreateBetOutcomeParams{ + BetID: outcome.BetID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) + } + rows, err := s.queries.CreateBetOutcome(ctx, dbParams) + if err != nil { - return domain.Bet{}, err + return rows, err } - return convertDBBet(bet), nil + return rows, nil } -func (s *Store) GetAllBets(ctx context.Context) ([]domain.Bet, error) { +func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { + bet, err := s.queries.GetBetByID(ctx, id) + if err != nil { + return domain.GetBet{}, err + } + + return convertDBBetOutcomes(bet), nil +} + +func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) { bets, err := s.queries.GetAllBets(ctx) if err != nil { return nil, err } - var result []domain.Bet = make([]domain.Bet, 0, len(bets)) + var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) for _, bet := range bets { - result = append(result, convertDBBet(bet)) + result = append(result, convertDBBetOutcomes(bet)) + } + + return result, nil +} + +func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) { + bets, err := s.queries.GetBetByBranchID(ctx, pgtype.Int8{ + Int64: BranchID, + Valid: true, + }) + + if err != nil { + return nil, err + } + + var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) + for _, bet := range bets { + result = append(result, convertDBBetOutcomes(bet)) } return result, nil diff --git a/internal/repository/branch.go b/internal/repository/branch.go index 6268615..6287380 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -154,6 +154,18 @@ func (s *Store) CreateSupportedOperation(ctx context.Context, supportedOperation }, nil } +func (s *Store) CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error { + _, err := s.queries.CreateBranchCashier(ctx, dbgen.CreateBranchCashierParams{ + UserID: userID, + BranchID: branchID, + }) + + if err != nil { + return err + } + return nil +} + func (s *Store) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { dbOperations, err := s.queries.GetAllSupportedOperations(ctx) if err != nil { @@ -188,6 +200,16 @@ func (s *Store) GetBranchOperations(ctx context.Context, branchID int64) ([]doma return branchOperations, nil } +func (s *Store) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) { + branch, err := s.queries.GetBranchByCashier(ctx, userID) + if err != nil { + return domain.Branch{}, err + } + + return convertDBBranch(branch), err +} + + func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { err := s.queries.DeleteBranchOperation(ctx, dbgen.DeleteBranchOperationParams{ BranchID: branchID, @@ -195,3 +217,8 @@ func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, opera }) return err } + +func (s *Store) DeleteBranchCashier(ctx context.Context, userID int64) error { + return s.queries.DeleteBranchCashier(ctx, userID) + +} diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index b7945c3..a2d5c1a 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -14,6 +14,27 @@ func convertDBTicket(ticket dbgen.Ticket) domain.Ticket { Amount: domain.Currency(ticket.Amount.Int64), TotalOdds: ticket.TotalOdds, } + +} + +func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket { + + var outcomes []domain.TicketOutcome = make([]domain.TicketOutcome, 0, len(ticket.Outcomes)) + + for _, outcome := range ticket.Outcomes { + outcomes = append(outcomes, domain.TicketOutcome{ + ID: outcome.ID, + TicketID: outcome.TicketID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) + } + return domain.GetTicket{ + ID: ticket.ID, + Amount: domain.Currency(ticket.Amount.Int64), + TotalOdds: ticket.TotalOdds, + Outcomes: outcomes, + } } func convertCreateTicket(ticket domain.CreateTicket) dbgen.CreateTicketParams { @@ -35,25 +56,45 @@ func (s *Store) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (d } -func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) { - ticket, err := s.queries.GetTicketByID(ctx, id) - if err != nil { - return domain.Ticket{}, err +func (s *Store) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) { + + var dbParams []dbgen.CreateTicketOutcomeParams = make([]dbgen.CreateTicketOutcomeParams, 0, len(outcomes)) + for _, outcome := range outcomes { + dbParams = append(dbParams, dbgen.CreateTicketOutcomeParams{ + TicketID: outcome.TicketID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) } - return convertDBTicket(ticket), nil + rows, err := s.queries.CreateTicketOutcome(ctx, dbParams) + + if err != nil { + return rows, err + } + + return rows, nil } -func (s *Store) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { +func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) { + ticket, err := s.queries.GetTicketByID(ctx, id) + if err != nil { + return domain.GetTicket{}, err + } + + return convertDBTicketOutcomes(ticket), nil +} + +func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { tickets, err := s.queries.GetAllTickets(ctx) if err != nil { return nil, err } - var result []domain.Ticket = make([]domain.Ticket, 0, len(tickets)) + var result []domain.GetTicket = make([]domain.GetTicket, 0, len(tickets)) for _, ticket := range tickets { - result = append(result, convertDBTicket(ticket)) + result = append(result, convertDBTicketOutcomes(ticket)) } return result, nil diff --git a/internal/repository/user.go b/internal/repository/user.go index c3aeacd..1b20834 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -102,6 +102,44 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U return userList, nil } +func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) { + users, err := s.queries.GetAllCashiers(ctx) + if err != nil { + return nil, err + } + userList := make([]domain.User, len(users)) + for i, user := range users { + userList[i] = domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + } + } + return userList, nil +} + +func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) { + users, err := s.queries.GetCashiersByBranch(ctx, branchID) + if err != nil { + return nil, err + } + userList := make([]domain.User, len(users)) + for i, user := range users { + userList[i] = domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + } + } + return userList, nil +} + func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) { users, err := s.queries.SearchUserByNameOrPhone(ctx, pgtype.Text{ String: searchString, @@ -234,6 +272,40 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password } return nil } -func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.CreateUserReq) (domain.User, error) { - return domain.User{}, nil +func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) { + userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ + FirstName: user.FirstName, + LastName: user.LastName, + Email: pgtype.Text{ + String: user.Email, + Valid: user.Email != "", + }, + PhoneNumber: pgtype.Text{ + String: user.PhoneNumber, + Valid: user.PhoneNumber != "", + }, + Password: user.Password, + Role: string(user.Role), + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + }, + UpdatedAt: pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + }, + }) + if err != nil { + return domain.User{}, err + } + return domain.User{ + ID: userRes.ID, + FirstName: userRes.FirstName, + LastName: userRes.LastName, + Email: userRes.Email.String, + PhoneNumber: userRes.PhoneNumber.String, + Role: domain.Role(userRes.Role), + }, nil } diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 97a87e5..86bf670 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -129,6 +129,31 @@ func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64, company return convertDBGetCustomerWallet(customerWallet), nil } +func (s *Store) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) { + wallets, err := s.queries.GetAllBranchWallets(ctx) + if err != nil { + return nil, err + } + + var result []domain.BranchWallet = make([]domain.BranchWallet, 0, len(wallets)) + + for _, wallet := range wallets { + result = append(result, domain.BranchWallet{ + ID: wallet.ID, + Balance: domain.Currency(wallet.Balance), + IsActive: wallet.IsActive, + UpdatedAt: wallet.UpdatedAt.Time, + CreatedAt: wallet.CreatedAt.Time, + Name: wallet.Name, + Location: wallet.Location, + BranchManagerID: wallet.BranchManagerID, + CompanyID: wallet.CompanyID, + IsSelfOwned: wallet.IsSelfOwned, + }) + } + return result, nil +} + func (s *Store) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { err := s.queries.UpdateBalance(ctx, dbgen.UpdateBalanceParams{ ID: id, diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 1061b45..2c7c133 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -8,8 +8,10 @@ import ( type BetStore interface { CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) - GetBetByID(ctx context.Context, id int64) (domain.Bet, error) - GetAllBets(ctx context.Context) ([]domain.Bet, error) + CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) + GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) + GetAllBets(ctx context.Context) ([]domain.GetBet, error) + GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error DeleteBet(ctx context.Context, id int64) error } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 58b9cc5..83cbb27 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -19,13 +19,22 @@ func NewService(betStore BetStore) *Service { func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { return s.betStore.CreateBet(ctx, bet) } -func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.Bet, error) { + +func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) { + return s.betStore.CreateBetOutcome(ctx, outcomes) +} + +func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { return s.betStore.GetBetByID(ctx, id) } -func (s *Service) GetAllBets(ctx context.Context) ([]domain.Bet, error) { +func (s *Service) GetAllBets(ctx context.Context) ([]domain.GetBet, error) { return s.betStore.GetAllBets(ctx) } +func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domain.GetBet, error) { + return s.betStore.GetBetByBranchID(ctx, branchID) +} + func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go index e2f2ec1..53e6b0d 100644 --- a/internal/services/branch/port.go +++ b/internal/services/branch/port.go @@ -8,16 +8,19 @@ import ( type BranchStore interface { CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) - CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) - CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) - GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) - GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) DeleteBranch(ctx context.Context, id int64) error + CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error + CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) + GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) + GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error + CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error + GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) + DeleteBranchCashier(ctx context.Context, userID int64) error } diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go index 83d97e2..eddd1e9 100644 --- a/internal/services/branch/service.go +++ b/internal/services/branch/service.go @@ -25,6 +25,11 @@ func (s *Service) CreateSupportedOperation(ctx context.Context, supportedOperati func (s *Service) CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error { return s.branchStore.CreateBranchOperation(ctx, branchOperation) } + +func (s *Service) CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error { + return s.branchStore.CreateBranchCashier(ctx, branchID, userID) +} + func (s *Service) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { return s.branchStore.GetBranchByID(ctx, id) } @@ -41,6 +46,10 @@ func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, er return s.branchStore.GetAllBranches(ctx) } +func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) { + return s.branchStore.GetBranchByCashier(ctx, userID) +} + func (s *Service) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { return s.branchStore.GetAllSupportedOperations(ctx) } @@ -57,3 +66,7 @@ func (s *Service) DeleteBranch(ctx context.Context, id int64) error { func (s *Service) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { return s.branchStore.DeleteBranchOperation(ctx, branchID, operationID) } + +func (s *Service) DeleteBranchCashier(ctx context.Context, userID int64) error { + return s.branchStore.DeleteBranchCashier(ctx, userID) +} diff --git a/internal/services/ticket/port.go b/internal/services/ticket/port.go index 042d27a..ae531c6 100644 --- a/internal/services/ticket/port.go +++ b/internal/services/ticket/port.go @@ -8,8 +8,9 @@ import ( type TicketStore interface { CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) - GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) - GetAllTickets(ctx context.Context) ([]domain.Ticket, error) + 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) 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 5779ce4..46036f4 100644 --- a/internal/services/ticket/service.go +++ b/internal/services/ticket/service.go @@ -19,10 +19,15 @@ func NewService(ticketStore TicketStore) *Service { func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) { return s.ticketStore.CreateTicket(ctx, ticket) } -func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) { + +func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) { + return s.ticketStore.CreateTicketOutcome(ctx, outcomes) +} + +func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) { return s.ticketStore.GetTicketByID(ctx, id) } -func (s *Service) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { +func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { return s.ticketStore.GetAllTickets(ctx) } func (s *Service) DeleteTicket(ctx context.Context, id int64) error { diff --git a/internal/services/user/direct.go b/internal/services/user/direct.go index b5fca74..f848792 100644 --- a/internal/services/user/direct.go +++ b/internal/services/user/direct.go @@ -19,8 +19,21 @@ func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq) (do // User.BranchID = branchId // User.Role = string(domain.RoleBranchManager) // } + hashedPassword, err := hashPassword(User.Password) + if err != nil { + return domain.User{}, err + } - return s.userStore.CreateUserWithoutOtp(ctx, User) + return s.userStore.CreateUserWithoutOtp(ctx, domain.User{ + FirstName: User.FirstName, + LastName: User.LastName, + Email: User.Email, + PhoneNumber: User.PhoneNumber, + Password: hashedPassword, + Role: domain.Role(User.Role), + EmailVerified: true, + PhoneVerified: true, + }) } func (s *Service) DeleteUser(ctx context.Context, id int64) error { @@ -51,3 +64,11 @@ func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error return s.userStore.GetUserByID(ctx, id) } + +func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) { + return s.userStore.GetCashiersByBranch(ctx, branchID) +} + +func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.User, error) { + return s.userStore.GetAllCashiers(ctx) +} diff --git a/internal/services/user/port.go b/internal/services/user/port.go index 7d4c5e3..9b8d06e 100644 --- a/internal/services/user/port.go +++ b/internal/services/user/port.go @@ -8,9 +8,11 @@ import ( type UserStore interface { CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) - CreateUserWithoutOtp(ctx context.Context, user domain.CreateUserReq) (domain.User, error) + CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, error) + GetAllCashiers(ctx context.Context) ([]domain.User, error) + GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error DeleteUser(ctx context.Context, id int64) error CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index b9eb043..9271039 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -13,6 +13,7 @@ type WalletStore interface { GetAllWallets(ctx context.Context) ([]domain.Wallet, error) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) + GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error UpdateWalletActive(ctx context.Context, id int64, isActive bool) error } @@ -24,4 +25,3 @@ type TransferStore interface { GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error } - diff --git a/internal/services/wallet/transfer.go b/internal/services/wallet/transfer.go index 6f0f0d4..387f255 100644 --- a/internal/services/wallet/transfer.go +++ b/internal/services/wallet/transfer.go @@ -23,10 +23,43 @@ func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfe return s.transferStore.GetTransferByID(ctx, id) } +func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { + return s.transferStore.GetTransfersByWallet(ctx, walletID) +} + func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { return s.transferStore.UpdateTransferVerification(ctx, id, verified) } +func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID) + if err != nil { + return domain.Transfer{}, err + } + + // Add to receiver + err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount) + if err != nil { + return domain.Transfer{}, err + } + + // Log the transfer so that if there is a mistake, it can be reverted + newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + CashierID: transfer.CashierID, + ReceiverWalletID: receiverWallet.ID, + Amount: transfer.Amount, + Type: domain.DEPOSIT, + PaymentMethod: transfer.PaymentMethod, + Verified: true, + }) + if err != nil { + return domain.Transfer{}, err + } + + return newTransfer, nil + +} + func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64) (domain.Transfer, error) { senderWallet, err := s.GetWalletByID(ctx, senderID) diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index c95a660..2644a39 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -7,7 +7,6 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) - var ( ErrBalanceInsufficient = errors.New("wallet balance is insufficient") ) @@ -62,6 +61,10 @@ func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, compa return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) } +func (s *Service) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) { + return s.walletStore.GetAllBranchWallets(ctx) +} + func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { return s.walletStore.UpdateBalance(ctx, id, balance) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index dab2e1d..efd3884 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -1,44 +1,106 @@ package handlers import ( + "encoding/json" "log/slog" "strconv" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "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" "github.com/google/uuid" ) +type BetOutcome struct { + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` +} +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 []int64 `json:"outcomes"` + Outcomes []BetOutcome `json:"outcomes"` Amount float32 `json:"amount" example:"100.0"` TotalOdds float32 `json:"total_odds" example:"4.22"` Status domain.BetStatus `json:"status" example:"1"` FullName string `json:"full_name" example:"John"` PhoneNumber string `json:"phone_number" example:"1234567890"` IsShopBet bool `json:"is_shop_bet" example:"false"` + BranchID NullableInt64 `json:"branch_id" example:"1"` } +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"` +} type BetRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []domain.Outcome `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"` + 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"` } -func convertBet(bet domain.Bet) BetRes { +func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes { + return CreateBetRes{ + ID: bet.ID, + Amount: bet.Amount.Float64(), + TotalOdds: bet.TotalOdds, + Status: bet.Status, + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + BranchID: bet.BranchID.Value, + UserID: bet.UserID.Value, + CreatedNumber: createdNumber, + } +} + +func convertBet(bet domain.GetBet) BetRes { return BetRes{ ID: bet.ID, - Outcomes: bet.Outcomes, Amount: bet.Amount.Float64(), TotalOdds: bet.TotalOdds, Status: bet.Status, @@ -60,15 +122,12 @@ func convertBet(bet domain.Bet) BetRes { // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /bet [post] -func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { +func CreateBet(logger *slog.Logger, betSvc *bet.Service, userSvc *user.Service, branchSvc *branch.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { - // TODO if user is customer, get id from the token then get the wallet id from there - // TODO: If user is a cashier, check the token, and find the role and get the branch id from there. Reduce amount from the branch wallet - - var isShopBet bool = true - var branchID int64 = 1 - var userID int64 + // Get user_id from middleware + userID := c.Locals("user_id").(int64) + var isShopBet bool var req CreateBetReq @@ -85,12 +144,46 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida return nil } + user, err := userSvc.GetUserByID(c.Context(), userID) + + if user.Role != domain.RoleCustomer { + isShopBet = true + if !req.BranchID.Valid { + logger.Error("CreateBetReq failed, branch id necessary") + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Branch ID necessary", + }) + } + + // Get the branch from the branch ID + branch, err := branchSvc.GetBranchByID(c.Context(), req.BranchID.Value) + if err != nil { + logger.Error("CreateBetReq failed, branch id invalid") + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Branch ID invalid", + }) + } + + // Deduct a percentage of the amount + var deductedAmount = req.Amount / 10 + err = walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.Currency(deductedAmount)) + + if err != nil { + logger.Error("CreateBetReq failed, unable to deduct from WalletID", branch.WalletID) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Unable to deduct from branch wallet", + }) + } + } else { + isShopBet = false + // TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount + } + // TODO Validate Outcomes Here and make sure they didn't expire cashoutUUID := uuid.New() bet, err := betSvc.CreateBet(c.Context(), domain.CreateBet{ - Outcomes: req.Outcomes, Amount: domain.Currency(req.Amount), TotalOdds: req.TotalOdds, Status: req.Status, @@ -98,7 +191,7 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida PhoneNumber: req.PhoneNumber, BranchID: domain.ValidInt64{ - Value: branchID, + Value: req.BranchID.Value, Valid: isShopBet, }, UserID: domain.ValidInt64{ @@ -113,8 +206,25 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida logger.Error("CreateBetReq failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } + var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes)) - res := convertBet(bet) + for _, outcome := range req.Outcomes { + outcomes = append(outcomes, domain.CreateBetOutcome{ + BetID: bet.ID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) + } + rows, err := betSvc.CreateBetOutcome(c.Context(), outcomes) + + if err != nil { + logger.Error("CreateBetReq failed to create outcomes", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + res := convertCreateBet(bet, rows) return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) } diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 6a2bf16..ab97316 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" @@ -43,6 +44,7 @@ type BranchOperationRes struct { } type BranchRes struct { + ID int64 `json:"id" example:"1"` Name string `json:"name" example:"4-kilo Branch"` Location string `json:"location" example:"Addis Ababa"` WalletID int64 `json:"wallet_id" example:"1"` @@ -52,6 +54,7 @@ type BranchRes struct { } type BranchDetailRes struct { + ID int64 `json:"id" example:"1"` Name string `json:"name" example:"4-kilo Branch"` Location string `json:"location" example:"Addis Ababa"` WalletID int64 `json:"wallet_id" example:"1"` @@ -64,6 +67,7 @@ type BranchDetailRes struct { func convertBranch(branch domain.Branch) BranchRes { return BranchRes{ + ID: branch.ID, Name: branch.Name, Location: branch.Location, WalletID: branch.WalletID, @@ -75,6 +79,7 @@ func convertBranch(branch domain.Branch) BranchRes { func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes { return BranchDetailRes{ + ID: branch.ID, Name: branch.Name, Location: branch.Location, WalletID: branch.WalletID, @@ -393,6 +398,42 @@ func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *c } } +// SearchBranch godoc +// @Summary Search branches +// @Description Search branches by name or location +// @Tags branch +// @Accept json +// @Produce json +// @Param q query string true "Search query" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /search/branch [get] +func SearchBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // Get search query from request + searchQuery := c.Query("q") + if searchQuery == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Search query is required", nil, nil) + } + + // Call the service to search for branches + branches, err := branchSvc.SearchBranchByName(c.Context(), searchQuery) + if err != nil { + logger.Error("Failed to search branches", "query", searchQuery, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to search branches", err, nil) + } + + // Convert branches to response format + var result []BranchDetailRes + for _, branch := range branches { + result = append(result, convertBranchDetail(branch)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branches retrieved successfully", result, nil) + } +} + // GetAllSupportedOperations godoc // @Summary Gets all supported operations // @Description Gets all supported operations @@ -465,6 +506,41 @@ func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validat } } +// GetBetByBranchID godoc +// @Summary Gets bets by its branch id +// @Description Gets bets by its branch id +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BetRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/bets [get] +func GetBetByBranchID(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + if err != nil { + logger.Error("Invalid branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil) + } + + bets, err := betSvc.GetBetByBranchID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get bets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil) + } + + var res []BetRes = make([]BetRes, 0, len(bets)) + for _, bet := range bets { + res = append(res, convertBet(bet)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branch Bets Retrieved", res, nil) + } +} + // UpdateBranch godoc // @Summary Updates a branch // @Description Updates a branch diff --git a/internal/web_server/handlers/cashier.go b/internal/web_server/handlers/cashier.go index 6ad9874..b2b6418 100644 --- a/internal/web_server/handlers/cashier.go +++ b/internal/web_server/handlers/cashier.go @@ -3,8 +3,10 @@ package handlers import ( "log/slog" "strconv" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" @@ -17,6 +19,7 @@ type CreateCashierReq struct { Email string `json:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" example:"1234567890"` Password string `json:"password" example:"password123"` + BranchID int64 `json:"branch_id" example:"1"` } // CreateCashier godoc @@ -31,10 +34,9 @@ type CreateCashierReq struct { // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /cashiers [post] -func CreateCashier(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { +func CreateCashier(logger *slog.Logger, userSvc *user.Service, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { - creatorBranch := c.Locals("branch_id").(int64) var req CreateCashierReq if err := c.BodyParser(&req); err != nil { logger.Error("RegisterUser failed", "error", err) @@ -47,27 +49,49 @@ func CreateCashier(logger *slog.Logger, userSvc *user.Service, validator *custom response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } - user := domain.CreateUserReq{ + userRequest := domain.CreateUserReq{ FirstName: req.FirstName, LastName: req.LastName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, Role: string(domain.RoleCashier), - BranchID: creatorBranch, } - _, err := userSvc.CreateUser(c.Context(), user) + 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 } + + 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 + } + response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil) return nil } } +type GetCashierRes struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + PhoneNumber string `json:"phone_number"` + Role domain.Role `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + SuspendedAt time.Time `json:"suspended_at"` + Suspended bool `json:"suspended"` +} + // GetAllCashiers godoc // @Summary Get all cashiers // @Description Get all cashiers @@ -83,28 +107,48 @@ func CreateCashier(logger *slog.Logger, userSvc *user.Service, validator *custom // @Router /cashiers [get] func GetAllCashiers(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { - branchId := int64(12) //c.Locals("branch_id").(int64) - filter := user.Filter{ - Role: string(domain.RoleCashier), - BranchId: user.ValidBranchId{ - Value: branchId, - Valid: true, - }, - Page: c.QueryInt("page", 1), - PageSize: c.QueryInt("page_size", 10), - } - valErrs, ok := validator.Validate(c, filter) - if !ok { - response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - return nil - } - cashiers, err := userSvc.GetAllUsers(c.Context(), filter) + // branchId := int64(12) //c.Locals("branch_id").(int64) + // filter := user.Filter{ + // Role: string(domain.RoleCashier), + // BranchId: user.ValidBranchId{ + // Value: branchId, + // Valid: true, + // }, + // Page: c.QueryInt("page", 1), + // PageSize: c.QueryInt("page_size", 10), + // } + // valErrs, ok := validator.Validate(c, filter) + // if !ok { + // response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + // return 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 } - response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", cashiers, nil) + + var result []GetCashierRes + + for _, cashier := range cashiers { + result = append(result, GetCashierRes{ + ID: cashier.ID, + FirstName: cashier.FirstName, + LastName: cashier.LastName, + Email: cashier.Email, + PhoneNumber: cashier.PhoneNumber, + Role: cashier.Role, + EmailVerified: cashier.EmailVerified, + PhoneVerified: cashier.PhoneVerified, + CreatedAt: cashier.CreatedAt, + UpdatedAt: cashier.UpdatedAt, + SuspendedAt: cashier.SuspendedAt, + Suspended: cashier.Suspended, + }) + } + response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil) return nil } diff --git a/internal/web_server/handlers/manager.go b/internal/web_server/handlers/manager.go index d0ba39a..be428c9 100644 --- a/internal/web_server/handlers/manager.go +++ b/internal/web_server/handlers/manager.go @@ -17,7 +17,6 @@ type CreateManagerReq struct { Email string `json:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" example:"1234567890"` Password string `json:"password" example:"password123"` - BranchId int64 `json:"branch_id" example:"1"` } // CreateManagers godoc @@ -53,7 +52,6 @@ func CreateManager(logger *slog.Logger, userSvc *user.Service, validator *custom PhoneNumber: req.PhoneNumber, Password: req.Password, Role: string(domain.RoleBranchManager), - BranchID: req.BranchId, } _, err := userSvc.CreateUser(c.Context(), user) if err != nil { diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index 05339d4..a5bf943 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -11,13 +11,19 @@ import ( "github.com/gofiber/fiber/v2" ) +type TicketOutcome struct { + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` +} + type CreateTicketReq struct { - Outcomes []int64 `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` + Outcomes []TicketOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` } type CreateTicketRes struct { - FastCode int64 `json:"fast_code" example:"1234"` + FastCode int64 `json:"fast_code" example:"1234"` + CreatedNumber int64 `json:"created_number" example:"3"` } // CreateTicket godoc @@ -51,7 +57,6 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, // TODO Validate Outcomes Here and make sure they didn't expire ticket, err := ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ - Outcomes: req.Outcomes, Amount: domain.Currency(req.Amount), TotalOdds: req.TotalOdds, }) @@ -61,18 +66,37 @@ func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service, "error": "Internal server error", }) } + + var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) + + for _, outcome := range req.Outcomes { + outcomes = append(outcomes, domain.CreateTicketOutcome{ + TicketID: ticket.ID, + EventID: outcome.EventID, + OddID: outcome.OddID, + }) + } + rows, err := ticketSvc.CreateTicketOutcome(c.Context(), outcomes) + + if err != nil { + logger.Error("CreateTicketReq failed to create outcomes", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } res := CreateTicketRes{ - FastCode: ticket.ID, + FastCode: ticket.ID, + CreatedNumber: rows, } return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil) } } type TicketRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []domain.Outcome `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` + 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 diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index cd441fb..1b5e9fa 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -153,6 +153,9 @@ func GetAllTransactions( // Check user role and fetch transactions accordingly switch user.Role { + case domain.RoleSuperAdmin: + // Admin can fetch all transactions + transactions, err = transactionSvc.GetAllTransactions(c.Context()) case domain.RoleAdmin: // Admin can fetch all transactions transactions, err = transactionSvc.GetAllTransactions(c.Context()) diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go index 4ff7810..c3d71af 100644 --- a/internal/web_server/handlers/transfer_handler.go +++ b/internal/web_server/handlers/transfer_handler.go @@ -2,6 +2,7 @@ package handlers import ( "log/slog" + "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -12,7 +13,19 @@ import ( "github.com/gofiber/fiber/v2" ) -type TransferRes struct { +type TransferWalletRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + Verified bool `json:"verified" example:"true"` + Type string `json:"type" example:"transfer"` + PaymentMethod string `json:"payment_method" example:"bank"` + ReceiverWalletID int64 `json:"receiver_wallet_id" example:"1"` + SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` + CashierID *int64 `json:"cashier_id" example:"789"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` + UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:30:00Z"` +} +type RefillRes struct { ID int64 `json:"id" example:"1"` Amount float32 `json:"amount" example:"100.0"` Verified bool `json:"verified" example:"true"` @@ -25,7 +38,7 @@ type TransferRes struct { UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:30:00Z"` } -func convertTransfer(transfer domain.Transfer) TransferRes { +func convertTransfer(transfer domain.Transfer) TransferWalletRes { var senderWalletID *int64 if transfer.SenderWalletID.Valid { senderWalletID = &transfer.SenderWalletID.Value @@ -36,7 +49,7 @@ func convertTransfer(transfer domain.Transfer) TransferRes { cashierID = &transfer.CashierID.Value } - return TransferRes{ + return TransferWalletRes{ ID: transfer.ID, Amount: transfer.Amount.Float64(), Verified: transfer.Verified, @@ -51,11 +64,47 @@ func convertTransfer(transfer domain.Transfer) TransferRes { } type CreateTransferReq struct { - ReceiverID int64 `json:"receiver_id" example:"1"` Amount float64 `json:"amount" example:"100.0"` PaymentMethod string `json:"payment_method" example:"cash"` } +// GetTransfersByWallet godoc +// @Summary Get transfer by wallet +// @Description Get transfer by wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param transferToWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/wallet/{id} [get] +func GetTransfersByWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + walletID := c.Params("id") + + id, err := strconv.ParseInt(walletID, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + + transfers, err := walletSvc.GetTransfersByWallet(c.Context(), int64(id)) + if err != nil { + logger.Error("Failed to get transfers by wallet", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transfers", err, nil) + } + + var transferResponses []TransferWalletRes + for _, transfer := range transfers { + transferResponses = append(transferResponses, convertTransfer(transfer)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Transfers retrieved successfully", transferResponses, nil) + } +} + // TransferToWallet godoc // @Summary Create a transfer to wallet // @Description Create a transfer to wallet @@ -63,26 +112,42 @@ type CreateTransferReq struct { // @Accept json // @Produce json // @Param transferToWallet body CreateTransferReq true "Create Transfer" -// @Success 200 {object} TransferRes +// @Success 200 {object} TransferWalletRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /transfer/wallet [post] +// @Router /transfer/wallet/:id [post] func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { + + receiverIDString := c.Params("id") + + receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } // Get sender ID from the cashier userID := c.Locals("user_id").(int64) role := string(c.Locals("role").(domain.Role)) - branchID := c.Locals("branch_id").(int64) + + var senderID int64 if role == string(domain.RoleCustomer) { logger.Error("Unauthorized access", "userID", userID, "role", role) return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) - } - - branchWallet, err := branchSvc.GetBranchByID(c.Context(), branchID) - if err != nil { - logger.Error("Failed to get branch wallet", "branch ID", branchID, "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch wallet", err, nil) + } else if role == string(domain.RoleBranchManager) || role == string(domain.RoleAdmin) || role == string(domain.RoleSuperAdmin) { + // TODO Add a way for admins to reference branch wallet + senderID = 0 + logger.Error("Will", "userID", userID, "role", role) + return response.WriteJSON(c, fiber.StatusBadRequest, "Unauthorized access", nil, nil) + } else { + cashierBranch, err := branchSvc.GetBranchByCashier(c.Context(), userID) + if err != nil { + logger.Error("Failed to get branch", "user ID", userID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil) + } + senderID = cashierBranch.WalletID } var req CreateTransferReq @@ -100,7 +165,7 @@ func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc return nil } - transfer, err := walletSvc.TransferToWallet(c.Context(), branchWallet.ID, req.ReceiverID, domain.Currency(req.Amount), domain.PaymentMethod(req.PaymentMethod), domain.ValidInt64{Value: userID, Valid: true}) + transfer, err := walletSvc.TransferToWallet(c.Context(), senderID, receiverID, domain.Currency(req.Amount), domain.PaymentMethod(req.PaymentMethod), domain.ValidInt64{Value: userID, Valid: true}) if !ok { response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) @@ -113,3 +178,72 @@ func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc } } + +// RefillWallet godoc +// @Summary Refill wallet +// @Description Super Admin route to refill a wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param refillWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/refill/:id [post] +func RefillWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + receiverIDString := c.Params("id") + + receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + // Get sender ID from the cashier + userID := c.Locals("user_id").(int64) + role := string(c.Locals("role").(domain.Role)) + + if role != string(domain.RoleSuperAdmin) { + logger.Error("Unauthorized access", "userID", userID, "role", role) + return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + } + + var req CreateTransferReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateTransferReq failed", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request", + }) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + return nil + } + + transfer, err := walletSvc.RefillWallet(c.Context(), domain.CreateTransfer{ + Amount: domain.Currency(req.Amount), + PaymentMethod: domain.PaymentMethod(req.PaymentMethod), + ReceiverWalletID: receiverID, + CashierID: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + Type: domain.TransferType("deposit"), + }) + + if !ok { + response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) + return nil + } + + res := convertTransfer(transfer) + + return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) + + } +} diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index de655f6..37b72f4 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -66,6 +66,19 @@ func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes { } } +type BranchWalletRes struct { + ID int64 `json:"id" example:"1"` + Balance float32 `json:"balance" example:"100.0"` + IsActive bool `json:"is_active" example:"true"` + Name string `json:"name" example:"true"` + Location string `json:"location" example:"somewhere"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + // GetWalletByID godoc // @Summary Get wallet by ID // @Description Retrieve wallet details by wallet ID @@ -112,10 +125,12 @@ func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *cu // @Router /wallet [get] func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { + wallets, err := walletSvc.GetAllWallets(c.Context()) if err != nil { logger.Error("Failed to get wallets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) } var res []WalletRes = make([]WalletRes, 0, len(wallets)) @@ -128,6 +143,47 @@ func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *cu } } +// GetAllBranchWallets godoc +// @Summary Get all branch wallets +// @Description Retrieve all branch wallets +// @Tags wallet +// @Accept json +// @Produce json +// @Success 200 {array} WalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branchWallet [get] +func GetAllBranchWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + wallets, err := walletSvc.GetAllBranchWallets(c.Context()) + + if err != nil { + logger.Error("Failed to get wallets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) + } + + var res []BranchWalletRes = make([]BranchWalletRes, 0, len(wallets)) + + for _, wallet := range wallets { + res = append(res, BranchWalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsActive: wallet.IsActive, + Name: wallet.Name, + Location: wallet.Location, + BranchManagerID: wallet.BranchManagerID, + CompanyID: wallet.CompanyID, + IsSelfOwned: wallet.IsSelfOwned, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + }) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) + } +} + type UpdateWalletActiveReq struct { IsActive bool } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 7e3fd08..2e5fc08 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -42,7 +42,7 @@ func (a *App) initAppRoutes() { // //, a.authMiddleware a.fiber.Get("/cashiers", handlers.GetAllCashiers(a.logger, a.userSvc, a.validator)) - a.fiber.Post("/cashiers", handlers.CreateCashier(a.logger, a.userSvc, a.validator)) + a.fiber.Post("/cashiers", handlers.CreateCashier(a.logger, a.userSvc, a.branchSvc, a.validator)) a.fiber.Put("/cashiers/:id", handlers.UpdateCashier(a.logger, a.userSvc, a.validator)) // @@ -64,8 +64,11 @@ func (a *App) initAppRoutes() { a.fiber.Post("/branch", handlers.CreateBranch(a.logger, a.branchSvc, a.walletSvc, a.validator)) a.fiber.Get("/branch", handlers.GetAllBranches(a.logger, a.branchSvc, a.validator)) a.fiber.Get("/branch/:id", handlers.GetBranchByID(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/branch/:id/bets", handlers.GetBetByBranchID(a.logger, a.betSvc, a.validator)) a.fiber.Put("/branch/:id", handlers.UpdateBranch(a.logger, a.branchSvc, a.validator)) a.fiber.Delete("/branch/:id", handlers.DeleteBranch(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/search/branch", a.authMiddleware, handlers.SearchBranch(a.logger, a.branchSvc, a.validator)) + // /branch/search // branch/wallet // Branch Operation @@ -81,7 +84,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/ticket/:id", handlers.GetTicketByID(a.logger, a.ticketSvc, a.validator)) // Bet - a.fiber.Post("/bet", handlers.CreateBet(a.logger, a.betSvc, a.validator)) + a.fiber.Post("/bet", handlers.CreateBet(a.logger, a.betSvc, a.userSvc, a.branchSvc, a.walletSvc, a.validator)) a.fiber.Get("/bet", handlers.GetAllBet(a.logger, a.betSvc, a.validator)) a.fiber.Get("/bet/:id", handlers.GetBetByID(a.logger, a.betSvc, a.validator)) a.fiber.Patch("/bet/:id", handlers.UpdateCashOut(a.logger, a.betSvc, a.validator)) @@ -91,10 +94,13 @@ func (a *App) initAppRoutes() { a.fiber.Get("/wallet", handlers.GetAllWallets(a.logger, a.walletSvc, a.validator)) a.fiber.Get("/wallet/:id", handlers.GetWalletByID(a.logger, a.walletSvc, a.validator)) a.fiber.Put("/wallet/:id", handlers.UpdateWalletActive(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/branchWallet", handlers.GetAllBranchWallets(a.logger, a.walletSvc, a.validator)) // Transfer // /transfer/wallet - transfer from one wallet to another wallet - a.fiber.Post("/transfer/wallet", a.authMiddleware, handlers.TransferToWallet(a.logger, a.walletSvc, a.branchSvc, a.validator)) + a.fiber.Post("/transfer/wallet/:id", a.authMiddleware, handlers.TransferToWallet(a.logger, a.walletSvc, a.branchSvc, a.validator)) + a.fiber.Get("/transfer/wallet/:id", a.authMiddleware, handlers.GetTransfersByWallet(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/transfer/refill/:id", a.authMiddleware, handlers.RefillWallet(a.logger, a.walletSvc, a.validator)) // Transactions a.fiber.Post("/transaction", a.authMiddleware, handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator)) diff --git a/sqlc.yaml b/sqlc.yaml index bd998c2..5e2ff3a 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -16,4 +16,12 @@ sql: - db_type: "uuid" go_type: "github.com/google/uuid.NullUUID" nullable: true + - column: "bet_with_outcomes.outcomes" + go_type: + type: "BetOutcome" + slice: true + - column: "ticket_with_outcomes.outcomes" + go_type: + type: "TicketOutcome" + slice: true