diff --git a/cmd/main.go b/cmd/main.go index 7ac39e6..250a2b9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,6 +12,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" @@ -59,8 +60,9 @@ func main() { userSvc := user.NewService(store, store, mockSms, mockemail) ticketSvc := ticket.NewService(store) betSvc := bet.NewService(store) - walletSvc := wallet.NewService(store) + walletSvc := wallet.NewService(store, store) transactionSvc := transaction.NewService(store) + branchSvc := branch.NewService(store) notificationRepo := repository.NewNotificationRepository(store) notificationSvc := notificationservice.New(notificationRepo, logger, cfg) @@ -68,7 +70,7 @@ func main() { app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{ JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, - }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, + }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, notificationSvc, ) logger.Info("Starting server", "port", cfg.Port) diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index e4d5b9a..a061f7f 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -80,4 +80,6 @@ DROP TABLE IF EXISTS wallets; DROP TABLE IF EXISTS wallet_transfer; DROP TABLE IF EXISTS transactions; DROP TABLE IF EXISTS customer_wallets; +DROP TABLE IF EXISTS branches; + diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 59140c2..27ae8b1 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -78,6 +78,7 @@ CREATE TABLE IF NOT EXISTS wallets ( balance BIGINT NOT NULL DEFAULT 0, is_withdraw BOOLEAN NOT NULL, is_bettable BOOLEAN NOT NULL, + is_transferable BOOLEAN NOT NULL, user_id BIGINT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -99,10 +100,12 @@ CREATE TABLE IF NOT EXISTS customer_wallets ( CREATE TABLE IF NOT EXISTS wallet_transfer ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, - wallet_transfer VARCHAR(255) NOT NULL, - wallet_id BIGINT NOT NULL, + type VARCHAR(255) NOT NULL, + receiver_wallet_id BIGINT NOT NULL, + sender_wallet_id BIGINT, + cashier_id BIGINT, verified BOOLEAN NOT NULL DEFAULT false, - payment_method INT NOT NULL, + payment_method VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); diff --git a/db/query/transactions.sql b/db/query/transactions.sql index 2665eaa..75adba4 100644 --- a/db/query/transactions.sql +++ b/db/query/transactions.sql @@ -4,9 +4,12 @@ INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, payment_option, -- name: GetAllTransactions :many SELECT * FROM transactions; --- name: GetTransactionByID :one +-- name: GetTransactionByID :one SELECT * FROM transactions WHERE id = $1; +-- name: GetTransactionByBranch :many +SELECT * FROM transactions WHERE branch_id = $1; + -- name: UpdateTransactionVerified :exec UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; diff --git a/db/query/transfer.sql b/db/query/transfer.sql index 895ccb8..62007d6 100644 --- a/db/query/transfer.sql +++ b/db/query/transfer.sql @@ -1,11 +1,11 @@ -- name: CreateTransfer :one -INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING *; +INSERT INTO wallet_transfer (amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *; -- name: GetAllTransfers :many SELECT * FROM wallet_transfer; -- name: GetTransfersByWallet :many -SELECT * FROM wallet_transfer WHERE wallet_id = $1; +SELECT * FROM wallet_transfer WHERE receiver_wallet_id = $1 OR sender_wallet_id = $1; -- name: GetTransferByID :one SELECT * FROM wallet_transfer WHERE id = $1; diff --git a/db/query/wallet.sql b/db/query/wallet.sql index 46f3200..dc025e9 100644 --- a/db/query/wallet.sql +++ b/db/query/wallet.sql @@ -1,5 +1,5 @@ -- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) 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 *; diff --git a/docs/docs.go b/docs/docs.go index 36464a6..8cbb6b4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -399,6 +399,509 @@ const docTemplate = `{ } } }, + "/branch": { + "get": { + "description": "Gets all branches", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all branches", + "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" + } + } + } + }, + "post": { + "description": "Creates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a branch", + "parameters": [ + { + "description": "Creates branch", + "name": "createBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}": { + "get": { + "description": "Gets a single branch by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch by id", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Updates a branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Branch", + "name": "updateBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation": { + "get": { + "description": "Gets branch operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch operations", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation/{opID}": { + "delete": { + "description": "Delete the branch operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch operation", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Branch Operation ID", + "name": "opID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}/branch": { + "get": { + "description": "Gets branches by company id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by company id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "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" + } + } + } + } + }, + "/manager/{id}/branch": { + "get": { + "description": "Gets a branches by manager id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by manager id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "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" + } + } + } + } + }, + "/operation": { + "post": { + "description": "Creates a operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a operation", + "parameters": [ + { + "description": "Creates operation", + "name": "createBranchOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/supportedOperation": { + "post": { + "description": "Creates a supported operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a supported operation", + "parameters": [ + { + "description": "Creates supported operation", + "name": "createSupportedOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SupportedOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/ticket": { "get": { "description": "Retrieve all tickets", @@ -702,6 +1205,52 @@ const docTemplate = `{ } } }, + "/transfer/wallet": { + "post": { + "description": "Create a transfer to wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Create a transfer to wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -1257,6 +1806,85 @@ const docTemplate = `{ } } }, + "handlers.BranchDetailRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -1320,6 +1948,57 @@ const docTemplate = `{ } } }, + "handlers.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBranchReq": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + } + } + }, + "handlers.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.CreateTicketReq": { "type": "object", "properties": { @@ -1401,6 +2080,9 @@ const docTemplate = `{ } } }, + "handlers.CreateTransferReq": { + "type": "object" + }, "handlers.CustomerWalletRes": { "type": "object", "properties": { @@ -1520,6 +2202,23 @@ const docTemplate = `{ } } }, + "handlers.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -1603,6 +2302,51 @@ const docTemplate = `{ } } }, + "handlers.TransferRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "cashier_id": { + "type": "integer", + "example": 789 + }, + "created_at": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_method": { + "type": "string", + "example": "bank" + }, + "receiver_wallet_id": { + "type": "integer", + "example": 1 + }, + "sender_wallet_id": { + "type": "integer", + "example": 1 + }, + "type": { + "type": "string", + "example": "transfer" + }, + "updated_at": { + "type": "string", + "example": "2025-04-08T12:30:00Z" + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, "handlers.UpdateCashOutReq": { "type": "object", "properties": { @@ -1690,6 +2434,10 @@ const docTemplate = `{ "type": "boolean", "example": true }, + "is_transferable": { + "type": "boolean", + "example": true + }, "is_withdraw": { "type": "boolean", "example": true diff --git a/docs/swagger.json b/docs/swagger.json index cc51adb..21d28dc 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -391,6 +391,509 @@ } } }, + "/branch": { + "get": { + "description": "Gets all branches", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all branches", + "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" + } + } + } + }, + "post": { + "description": "Creates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a branch", + "parameters": [ + { + "description": "Creates branch", + "name": "createBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}": { + "get": { + "description": "Gets a single branch by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch by id", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Updates a branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Branch", + "name": "updateBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation": { + "get": { + "description": "Gets branch operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch operations", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation/{opID}": { + "delete": { + "description": "Delete the branch operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch operation", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Branch Operation ID", + "name": "opID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}/branch": { + "get": { + "description": "Gets branches by company id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by company id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "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" + } + } + } + } + }, + "/manager/{id}/branch": { + "get": { + "description": "Gets a branches by manager id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by manager id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "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" + } + } + } + } + }, + "/operation": { + "post": { + "description": "Creates a operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a operation", + "parameters": [ + { + "description": "Creates operation", + "name": "createBranchOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/supportedOperation": { + "post": { + "description": "Creates a supported operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a supported operation", + "parameters": [ + { + "description": "Creates supported operation", + "name": "createSupportedOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SupportedOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/ticket": { "get": { "description": "Retrieve all tickets", @@ -694,6 +1197,52 @@ } } }, + "/transfer/wallet": { + "post": { + "description": "Create a transfer to wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Create a transfer to wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -1249,6 +1798,85 @@ } } }, + "handlers.BranchDetailRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -1312,6 +1940,57 @@ } } }, + "handlers.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBranchReq": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + } + } + }, + "handlers.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.CreateTicketReq": { "type": "object", "properties": { @@ -1393,6 +2072,9 @@ } } }, + "handlers.CreateTransferReq": { + "type": "object" + }, "handlers.CustomerWalletRes": { "type": "object", "properties": { @@ -1512,6 +2194,23 @@ } } }, + "handlers.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -1595,6 +2294,51 @@ } } }, + "handlers.TransferRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "cashier_id": { + "type": "integer", + "example": 789 + }, + "created_at": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_method": { + "type": "string", + "example": "bank" + }, + "receiver_wallet_id": { + "type": "integer", + "example": 1 + }, + "sender_wallet_id": { + "type": "integer", + "example": 1 + }, + "type": { + "type": "string", + "example": "transfer" + }, + "updated_at": { + "type": "string", + "example": "2025-04-08T12:30:00Z" + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, "handlers.UpdateCashOutReq": { "type": "object", "properties": { @@ -1682,6 +2426,10 @@ "type": "boolean", "example": true }, + "is_transferable": { + "type": "boolean", + "example": true + }, "is_withdraw": { "type": "boolean", "example": true diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9b8b3d4..e6031ac 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -74,6 +74,63 @@ definitions: example: 2 type: integer type: object + handlers.BranchDetailRes: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + manager_name: + example: John Smith + type: string + manager_phone_number: + example: "0911111111" + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object + handlers.BranchOperationRes: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string + type: object + handlers.BranchRes: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object handlers.CheckPhoneEmailExistReq: properties: email: @@ -116,6 +173,42 @@ definitions: example: 4.22 type: number type: object + handlers.CreateBranchOperationReq: + properties: + branch_id: + example: 1 + type: integer + operation_id: + example: 1 + type: integer + type: object + handlers.CreateBranchReq: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + name: + example: 4-kilo Branch + type: string + type: object + handlers.CreateSupportedOperationReq: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string + type: object handlers.CreateTicketReq: properties: amount: @@ -171,6 +264,8 @@ definitions: reference_number: type: string type: object + handlers.CreateTransferReq: + type: object handlers.CustomerWalletRes: properties: company_id: @@ -255,6 +350,18 @@ definitions: phoneNumber: type: string type: object + handlers.SupportedOperationRes: + properties: + description: + example: Betting on sport events + type: string + id: + example: 1 + type: integer + name: + example: SportsBook + type: string + type: object handlers.TicketRes: properties: amount: @@ -312,6 +419,39 @@ definitions: example: true type: boolean type: object + handlers.TransferRes: + properties: + amount: + example: 100 + type: number + cashier_id: + example: 789 + type: integer + created_at: + example: "2025-04-08T12:00:00Z" + type: string + id: + example: 1 + type: integer + payment_method: + example: bank + type: string + receiver_wallet_id: + example: 1 + type: integer + sender_wallet_id: + example: 1 + type: integer + type: + example: transfer + type: string + updated_at: + example: "2025-04-08T12:30:00Z" + type: string + verified: + example: true + type: boolean + type: object handlers.UpdateCashOutReq: properties: cashedOut: @@ -370,6 +510,9 @@ definitions: is_bettable: example: true type: boolean + is_transferable: + example: true + type: boolean is_withdraw: example: true type: boolean @@ -689,6 +832,338 @@ paths: summary: Updates the cashed out field tags: - bet + /branch: + get: + consumes: + - application/json + description: Gets all branches + 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: Gets all branches + tags: + - branch + post: + consumes: + - application/json + description: Creates a branch + parameters: + - description: Creates branch + in: body + name: createBranch + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a branch + tags: + - branch + /branch/{id}: + delete: + consumes: + - application/json + description: Delete the branch + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the branch + tags: + - branch + get: + consumes: + - application/json + description: Gets a single branch by id + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchDetailRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branch by id + tags: + - branch + put: + consumes: + - application/json + description: Updates a branch + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + - description: Update Branch + in: body + name: updateBranch + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Updates a branch + tags: + - branch + /branch/{id}/operation: + get: + consumes: + - application/json + description: Gets branch operations + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchOperationRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branch operations + tags: + - branch + /branch/{id}/operation/{opID}: + delete: + consumes: + - application/json + description: Delete the branch operation + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + - description: Branch Operation ID + in: path + name: opID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the branch operation + tags: + - branch + /company/{id}/branch: + get: + consumes: + - application/json + description: Gets branches by company id + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + 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: Gets branches by company id + tags: + - branch + /manager/{id}/branch: + get: + consumes: + - application/json + description: Gets a branches by manager id + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + 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: Gets branches by manager id + tags: + - branch + /operation: + post: + consumes: + - application/json + description: Creates a operation + parameters: + - description: Creates operation + in: body + name: createBranchOperation + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchOperationReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchOperationRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a operation + tags: + - branch + /supportedOperation: + post: + consumes: + - application/json + description: Creates a supported operation + parameters: + - description: Creates supported operation + in: body + name: createSupportedOperation + required: true + schema: + $ref: '#/definitions/handlers.CreateSupportedOperationReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.SupportedOperationRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a supported operation + tags: + - branch /ticket: get: consumes: @@ -889,6 +1364,36 @@ paths: summary: Updates the cashed out field tags: - transaction + /transfer/wallet: + post: + consumes: + - application/json + description: Create a transfer to 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.TransferRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a transfer to wallet + tags: + - transfer /user/checkPhoneEmailExist: post: consumes: diff --git a/gen/db/models.go b/gen/db/models.go index 2f66efc..8e449a6 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -155,23 +155,26 @@ type User struct { } type Wallet struct { - ID int64 - Balance int64 - IsWithdraw bool - IsBettable bool - UserID int64 - IsActive bool - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp -} - -type WalletTransfer struct { ID int64 - Amount int64 - WalletTransfer string - WalletID int64 - Verified bool - PaymentMethod int32 + Balance int64 + IsWithdraw bool + IsBettable bool + IsTransferable bool + UserID int64 + IsActive bool CreatedAt pgtype.Timestamp UpdatedAt pgtype.Timestamp } + +type WalletTransfer struct { + ID int64 + Amount int64 + Type string + ReceiverWalletID int64 + SenderWalletID pgtype.Int8 + CashierID pgtype.Int8 + Verified bool + PaymentMethod string + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go index 31b535c..4f5fbe1 100644 --- a/gen/db/transactions.sql.go +++ b/gen/db/transactions.sql.go @@ -106,6 +106,47 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) return items, nil } +const GetTransactionByBranch = `-- name: GetTransactionByBranch :many +SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE branch_id = $1 +` + +func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) { + rows, err := q.db.Query(ctx, GetTransactionByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CashierID, + &i.BetID, + &i.PaymentOption, + &i.FullName, + &i.PhoneNumber, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetTransactionByID = `-- name: GetTransactionByID :one SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 ` diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index 770f23c..9faeaf5 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -7,26 +7,42 @@ package dbgen import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) const CreateTransfer = `-- name: CreateTransfer :one -INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING id, amount, wallet_transfer, wallet_id, verified, payment_method, created_at, updated_at +INSERT INTO wallet_transfer (amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at ` type CreateTransferParams struct { - Amount int64 - WalletTransfer string - WalletID int64 + Amount int64 + Type string + ReceiverWalletID int64 + SenderWalletID pgtype.Int8 + CashierID pgtype.Int8 + Verified bool + PaymentMethod string } func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) { - row := q.db.QueryRow(ctx, CreateTransfer, arg.Amount, arg.WalletTransfer, arg.WalletID) + row := q.db.QueryRow(ctx, CreateTransfer, + arg.Amount, + arg.Type, + arg.ReceiverWalletID, + arg.SenderWalletID, + arg.CashierID, + arg.Verified, + arg.PaymentMethod, + ) var i WalletTransfer err := row.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, &i.PaymentMethod, &i.CreatedAt, @@ -36,7 +52,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) } const GetAllTransfers = `-- name: GetAllTransfers :many -SELECT id, amount, wallet_transfer, wallet_id, verified, payment_method, created_at, updated_at FROM wallet_transfer +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer ` func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) { @@ -51,8 +67,10 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) if err := rows.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, &i.PaymentMethod, &i.CreatedAt, @@ -69,7 +87,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) } const GetTransferByID = `-- name: GetTransferByID :one -SELECT id, amount, wallet_transfer, wallet_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE id = $1 +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE id = $1 ` func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer, error) { @@ -78,8 +96,10 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer err := row.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, &i.PaymentMethod, &i.CreatedAt, @@ -89,11 +109,11 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer } const GetTransfersByWallet = `-- name: GetTransfersByWallet :many -SELECT id, amount, wallet_transfer, wallet_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE wallet_id = $1 +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE receiver_wallet_id = $1 OR sender_wallet_id = $1 ` -func (q *Queries) GetTransfersByWallet(ctx context.Context, walletID int64) ([]WalletTransfer, error) { - rows, err := q.db.Query(ctx, GetTransfersByWallet, walletID) +func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID int64) ([]WalletTransfer, error) { + rows, err := q.db.Query(ctx, GetTransfersByWallet, receiverWalletID) if err != nil { return nil, err } @@ -104,8 +124,10 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, walletID int64) ([]W if err := rows.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, &i.PaymentMethod, &i.CreatedAt, diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index de555d9..0700a07 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -43,23 +43,30 @@ func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWa } const CreateWallet = `-- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING id, balance, is_withdraw, is_bettable, 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 { - IsWithdraw bool - IsBettable bool - UserID int64 + IsWithdraw bool + IsBettable bool + IsTransferable bool + UserID int64 } func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wallet, error) { - row := q.db.QueryRow(ctx, CreateWallet, arg.IsWithdraw, arg.IsBettable, arg.UserID) + row := q.db.QueryRow(ctx, CreateWallet, + arg.IsWithdraw, + arg.IsBettable, + arg.IsTransferable, + arg.UserID, + ) var i Wallet err := row.Scan( &i.ID, &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -69,7 +76,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal } const GetAllWallets = `-- name: GetAllWallets :many -SELECT id, balance, is_withdraw, is_bettable, 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) { @@ -86,6 +93,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -156,7 +164,7 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletPa } const GetWalletByID = `-- name: GetWalletByID :one -SELECT id, balance, is_withdraw, is_bettable, 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) { @@ -167,6 +175,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -176,7 +185,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { } const GetWalletByUserID = `-- name: GetWalletByUserID :many -SELECT id, balance, is_withdraw, is_bettable, 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) { @@ -193,6 +202,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, diff --git a/internal/domain/branch.go b/internal/domain/branch.go index 29143c0..3295dae 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -43,6 +43,14 @@ type CreateBranch struct { IsSelfOwned bool } +type UpdateBranch struct { + Name string + Location string + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool +} + type CreateSupportedOperation struct { Name string Description string diff --git a/internal/domain/transfer.go b/internal/domain/transfer.go index ed31614..845482b 100644 --- a/internal/domain/transfer.go +++ b/internal/domain/transfer.go @@ -7,34 +7,42 @@ type TransferType string const ( DEPOSIT TransferType = "deposit" WITHDRAW TransferType = "withdraw" + WALLET TransferType = "wallet" ) -type PaymentMethod int +type PaymentMethod string const ( - TRANSFER_CASH PaymentMethod = iota + 1 - TRANSFER_BANK - TRANSFER_CHAPA - TRANSFER_ARIFPAY - TRANSFER_SANTIM - TRANSFER_ADDISPAY - TRANSFER_OTHER + TRANSFER_CASH PaymentMethod = "cash" + TRANSFER_BANK PaymentMethod = "bank" + TRANSFER_CHAPA PaymentMethod = "chapa" + TRANSFER_ARIFPAY PaymentMethod = "arifpay" + TRANSFER_SANTIMPAY PaymentMethod = "santimpay" + TRANSFER_ADDISPAY PaymentMethod = "addispay" + TRANSFER_OTHER PaymentMethod = "other" ) +// There is always a receiving wallet id +// There is a sender wallet id only if wallet transfer type type Transfer struct { - ID int64 - Amount Currency - Verified bool - WalletID int64 - Type TransferType - PaymentMethod PaymentMethod - CreatedAt time.Time - UpdatedAt time.Time + ID int64 + Amount Currency + Verified bool + Type TransferType + PaymentMethod PaymentMethod + ReceiverWalletID int64 + SenderWalletID ValidInt64 + CashierID ValidInt64 + CreatedAt time.Time + UpdatedAt time.Time } type CreateTransfer struct { - Amount Currency - Verified bool - WalletID int64 - Type TransferType + Amount Currency + Verified bool + ReceiverWalletID int64 + SenderWalletID ValidInt64 + CashierID ValidInt64 + Type TransferType + PaymentMethod PaymentMethod } diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index ff92ef6..efb80ab 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -7,6 +7,7 @@ type Wallet struct { Balance Currency IsWithdraw bool IsBettable bool + IsTransferable bool IsActive bool UserID int64 UpdatedAt time.Time @@ -36,6 +37,7 @@ type GetCustomerWallet struct { type CreateWallet struct { IsWithdraw bool IsBettable bool + IsTransferable bool UserID int64 } diff --git a/internal/repository/branch.go b/internal/repository/branch.go index 2a8d470..a37493a 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -136,7 +136,7 @@ func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, erro return branches, nil } -func (s *Store) UpdateBranch(ctx context.Context, id int64, branch domain.CreateBranch) (domain.Branch, error) { +func (s *Store) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) { dbBranch, err := s.queries.UpdateBranch(ctx, dbgen.UpdateBranchParams{ ID: id, Name: branch.Name, diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go index 87f04f7..f0faad3 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/transaction.go @@ -72,6 +72,19 @@ func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, e } return result, nil } +func (s *Store) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { + transaction, err := s.queries.GetTransactionByBranch(ctx, id) + + if err != nil { + return nil, err + } + + var result []domain.Transaction = make([]domain.Transaction, len(transaction)) + for _, ticket := range transaction { + result = append(result, convertDBTransaction(ticket)) + } + return result, nil +} func (s *Store) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { err := s.queries.UpdateTransactionVerified(ctx, dbgen.UpdateTransactionVerifiedParams{ diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go index d39178b..70884f3 100644 --- a/internal/repository/transfer.go +++ b/internal/repository/transfer.go @@ -1,77 +1,96 @@ package repository -// import ( -// "context" +import ( + "context" -// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" -// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -// ) + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) -// func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { -// return domain.Transaction{ -// ID: transaction.ID, -// Amount: domain.Currency(transaction.Amount), -// Type: domain.TransactionType(transaction.TransactionType), -// Verified: transaction.Verified, -// WalletID: transaction.WalletID, -// } -// } +func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer { + return domain.Transfer{ + ID: transfer.ID, + Amount: domain.Currency(transfer.Amount), + Type: domain.TransferType(transfer.Type), + Verified: transfer.Verified, + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: domain.ValidInt64{ + Value: transfer.SenderWalletID.Int64, + Valid: transfer.SenderWalletID.Valid, + }, + CashierID: domain.ValidInt64{ + Value: transfer.CashierID.Int64, + Valid: transfer.CashierID.Valid, + }, + PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod), + } +} -// func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { -// return dbgen.CreateTransactionParams{ -// Amount: int64(transaction.Amount), -// TransactionType: string(transaction.Type), -// WalletID: transaction.WalletID, -// } -// } +func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams { + return dbgen.CreateTransferParams{ + Amount: int64(transfer.Amount), + Type: string(transfer.Type), + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: pgtype.Int8{ + Int64: transfer.SenderWalletID.Value, + Valid: transfer.SenderWalletID.Valid, + }, + CashierID: pgtype.Int8{ + Int64: transfer.CashierID.Value, + Valid: transfer.CashierID.Valid, + }, + PaymentMethod: string(transfer.PaymentMethod), + } +} -// func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { -// newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) -// if err != nil { -// return domain.Transaction{}, err -// } -// return convertDBTransaction(newTransaction), nil -// } +func (s *Store) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + newTransfer, err := s.queries.CreateTransfer(ctx, convertCreateTransfer(transfer)) + if err != nil { + return domain.Transfer{}, err + } + return convertDBTransfer(newTransfer), nil +} -// func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { -// transactions, err := s.queries.GetAllTransactions(ctx) -// if err != nil { -// return nil, err -// } -// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) +func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { + transfers, err := s.queries.GetAllTransfers(ctx) + if err != nil { + return nil, err + } + var result []domain.Transfer = make([]domain.Transfer, len(transfers)) -// for _, transaction := range transactions { -// result = append(result, convertDBTransaction(transaction)) -// } -// return result, nil -// } -// func (s *Store) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) { -// transactions, err := s.queries.GetTransactionsByWallet(ctx, walletID) -// if err != nil { -// return nil, err -// } + for _, transfer := range transfers { + result = append(result, convertDBTransfer(transfer)) + } + return result, nil +} +func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { + transfers, err := s.queries.GetTransfersByWallet(ctx, walletID) + if err != nil { + return nil, err + } -// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) + var result []domain.Transfer = make([]domain.Transfer, len(transfers)) -// for _, transaction := range transactions { -// result = append(result, convertDBTransaction(transaction)) -// } -// return result, nil -// } + for _, transfer := range transfers { + result = append(result, convertDBTransfer(transfer)) + } + return result, nil +} -// func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { -// transaction, err := s.queries.GetTransactionByID(ctx, id) -// if err != nil { -// return domain.Transaction{}, nil -// } -// return convertDBTransaction(transaction), nil -// } +func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { + transfer, err := s.queries.GetTransferByID(ctx, id) + if err != nil { + return domain.Transfer{}, nil + } + return convertDBTransfer(transfer), nil +} -// func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { -// err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ -// ID: id, -// Verified: verified, -// }) +func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ + ID: id, + Verified: verified, + }) -// return err -// } + return err +} diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 87f88ac..7126fc4 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -9,22 +9,24 @@ import ( func convertDBWallet(wallet dbgen.Wallet) domain.Wallet { return domain.Wallet{ - ID: wallet.ID, - Balance: domain.Currency(wallet.Balance), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt.Time, - CreatedAt: wallet.CreatedAt.Time, + ID: wallet.ID, + Balance: domain.Currency(wallet.Balance), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt.Time, + CreatedAt: wallet.CreatedAt.Time, } } func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams { return dbgen.CreateWalletParams{ - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - UserID: wallet.UserID, + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + UserID: wallet.UserID, } } diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go index 6cff893..633fa31 100644 --- a/internal/services/branch/port.go +++ b/internal/services/branch/port.go @@ -15,7 +15,7 @@ type BranchStore interface { 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) - UpdateBranch(ctx context.Context, id int64, branch domain.CreateBranch) (domain.Branch, error) + UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) DeleteBranch(ctx context.Context, id int64) error DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error } diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go index f020252..49a87ff 100644 --- a/internal/services/branch/service.go +++ b/internal/services/branch/service.go @@ -28,7 +28,7 @@ func (s *Service) CreateBranchOperation(ctx context.Context, branchOperation dom func (s *Service) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { return s.branchStore.GetBranchByID(ctx, id) } -func (s *Service) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([] domain.BranchDetail, error) { +func (s *Service) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) { return s.branchStore.GetBranchByManagerID(ctx, branchManagerID) } func (s *Service) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) { @@ -40,7 +40,7 @@ func (s *Service) GetBranchOperations(ctx context.Context, branchID int64) ([]do func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { return s.branchStore.GetAllBranches(ctx) } -func (s *Service) UpdateBranch(ctx context.Context, id int64, branch domain.CreateBranch) (domain.Branch, error) { +func (s *Service) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) { return s.branchStore.UpdateBranch(ctx, id, branch) } func (s *Service) DeleteBranch(ctx context.Context, id int64) error { diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go index 27b9f5a..cbd9a0f 100644 --- a/internal/services/transaction/port.go +++ b/internal/services/transaction/port.go @@ -10,5 +10,6 @@ type TransactionStore interface { CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) + GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error } diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 31ca58e..2c33917 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -25,6 +25,9 @@ func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Tran func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { return s.transactionStore.GetAllTransactions(ctx) } +func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { + return s.transactionStore.GetTransactionByBranch(ctx, id) +} func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { return s.transactionStore.UpdateTransactionVerified(ctx, id, verified) diff --git a/internal/services/transfer/chapa.go b/internal/services/transfer/chapa.go deleted file mode 100644 index 3e884d3..0000000 --- a/internal/services/transfer/chapa.go +++ /dev/null @@ -1 +0,0 @@ -package transfer diff --git a/internal/services/transfer/port.go b/internal/services/transfer/port.go deleted file mode 100644 index 213d65a..0000000 --- a/internal/services/transfer/port.go +++ /dev/null @@ -1,15 +0,0 @@ -package transfer - -import ( - "context" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - -type TransferStore interface { - CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) - GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) - GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) - GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) - UpdateTransferVerification(ctx context.Context, id int64, verified bool) error -} diff --git a/internal/services/transfer/service.go b/internal/services/transfer/service.go deleted file mode 100644 index c628c42..0000000 --- a/internal/services/transfer/service.go +++ /dev/null @@ -1,33 +0,0 @@ -package transfer - -import ( - "context" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - -type Service struct { - transferStore TransferStore -} - -func NewService(transferStore TransferStore) *Service { - return &Service{ - transferStore: transferStore, - } -} - -func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { - return s.transferStore.CreateTransfer(ctx, transfer) -} - -func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { - return s.transferStore.GetAllTransfers(ctx) -} - -func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { - return s.transferStore.GetTransferByID(ctx, id) -} - -func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { - return s.transferStore.UpdateTransferVerification(ctx, id, verified) -} diff --git a/internal/services/wallet/chapa.go b/internal/services/wallet/chapa.go new file mode 100644 index 0000000..23a7507 --- /dev/null +++ b/internal/services/wallet/chapa.go @@ -0,0 +1 @@ +package wallet diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index 0e369bd..b9eb043 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -16,3 +16,12 @@ type WalletStore interface { UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error UpdateWalletActive(ctx context.Context, id int64, isActive bool) error } + +type TransferStore interface { + CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) + GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) + GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) + GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) + UpdateTransferVerification(ctx context.Context, id int64, verified bool) error +} + diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 1cb9ebe..a8913c2 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -1,102 +1,13 @@ package wallet -import ( - "context" - "errors" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - type Service struct { - walletStore WalletStore + walletStore WalletStore + transferStore TransferStore } -func NewService(walletStore WalletStore) *Service { +func NewService(walletStore WalletStore, transferStore TransferStore) *Service { return &Service{ - walletStore: walletStore, + walletStore: walletStore, + transferStore: transferStore, } } - -var ( - ErrBalanceInsufficient = errors.New("wallet balance is insufficient") -) - -func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { - return s.walletStore.CreateWallet(ctx, wallet) -} - -func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) { - - regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ - IsWithdraw: true, - IsBettable: true, - UserID: customerID, - }) - - if err != nil { - return domain.CustomerWallet{}, err - } - - staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ - IsWithdraw: false, - IsBettable: true, - UserID: customerID, - }) - - if err != nil { - return domain.CustomerWallet{}, err - } - - return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ - CustomerID: customerID, - CompanyID: companyID, - RegularWalletID: regularWallet.ID, - StaticWalletID: staticWallet.ID, - }) -} - -func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { - return s.walletStore.GetWalletByID(ctx, id) -} - -func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { - return s.walletStore.GetAllWallets(ctx) -} - -func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { - return s.walletStore.GetWalletsByUser(ctx, id) -} - -func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { - return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) -} - -func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { - return s.walletStore.UpdateBalance(ctx, id, balance) -} - -func (s *Service) Add(ctx context.Context, id int64, amount domain.Currency) error { - wallet, err := s.GetWalletByID(ctx, id) - if err != nil { - return err - } - - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) -} - -func (s *Service) Deduct(ctx context.Context, id int64, amount domain.Currency) error { - wallet, err := s.GetWalletByID(ctx, id) - if err != nil { - return err - } - - if wallet.Balance < amount { - return ErrBalanceInsufficient - } - - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) -} - -func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { - return s.walletStore.UpdateWalletActive(ctx, id, isActive) -} diff --git a/internal/services/wallet/transfer.go b/internal/services/wallet/transfer.go new file mode 100644 index 0000000..6f0f0d4 --- /dev/null +++ b/internal/services/wallet/transfer.go @@ -0,0 +1,86 @@ +package wallet + +import ( + "context" + "errors" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +var ( + ErrWalletNotTransferable = errors.New("wallet is not transferable") +) + +func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + return s.transferStore.CreateTransfer(ctx, transfer) +} + +func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { + return s.transferStore.GetAllTransfers(ctx) +} + +func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { + return s.transferStore.GetTransferByID(ctx, id) +} + +func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + return s.transferStore.UpdateTransferVerification(ctx, id, verified) +} + +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) + if err != nil { + return domain.Transfer{}, err + } + + if senderWallet.IsTransferable { + return domain.Transfer{}, ErrWalletNotTransferable + } + + receiverWallet, err := s.GetWalletByID(ctx, receiverID) + if err != nil { + return domain.Transfer{}, err + } + + if receiverWallet.IsTransferable { + return domain.Transfer{}, ErrWalletNotTransferable + } + + // Deduct from sender + if senderWallet.Balance < amount { + return domain.Transfer{}, ErrBalanceInsufficient + } + + err = s.walletStore.UpdateBalance(ctx, senderID, senderWallet.Balance-amount) + + if err != nil { + return domain.Transfer{}, err + } + + // Add to receiver + err = s.walletStore.UpdateBalance(ctx, receiverID, receiverWallet.Balance+amount) + + if err != nil { + return domain.Transfer{}, err + } + + // Log the transfer so that if there is a mistake, it can be reverted + transfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + SenderWalletID: domain.ValidInt64{ + Value: senderID, + Valid: true, + }, + CashierID: cashierID, + ReceiverWalletID: receiverID, + Amount: amount, + Type: domain.WALLET, + PaymentMethod: paymentMethod, + Verified: true, + }) + if err != nil { + return domain.Transfer{}, err + } + + return transfer, nil +} diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go new file mode 100644 index 0000000..c95a660 --- /dev/null +++ b/internal/services/wallet/wallet.go @@ -0,0 +1,93 @@ +package wallet + +import ( + "context" + "errors" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + + +var ( + ErrBalanceInsufficient = errors.New("wallet balance is insufficient") +) + +func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { + return s.walletStore.CreateWallet(ctx, wallet) +} + +func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) { + + regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: true, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ + CustomerID: customerID, + CompanyID: companyID, + RegularWalletID: regularWallet.ID, + StaticWalletID: staticWallet.ID, + }) +} + +func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { + return s.walletStore.GetWalletByID(ctx, id) +} + +func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { + return s.walletStore.GetAllWallets(ctx) +} + +func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { + return s.walletStore.GetWalletsByUser(ctx, id) +} + +func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { + return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) +} + +func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { + return s.walletStore.UpdateBalance(ctx, id, balance) +} + +func (s *Service) AddToWallet(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + +func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + if wallet.Balance < amount { + return ErrBalanceInsufficient + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + +func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { + return s.walletStore.UpdateWalletActive(ctx, id, isActive) +} diff --git a/internal/web_server/app.go b/internal/web_server/app.go index bdf1b33..53cac25 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -6,6 +6,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -16,6 +17,7 @@ import ( notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/bytedance/sonic" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" ) type App struct { @@ -29,6 +31,7 @@ type App struct { betSvc *bet.Service walletSvc *wallet.Service transactionSvc *transaction.Service + branchSvc *branch.Service validator *customvalidator.CustomValidator JwtConfig jwtutil.JwtConfig Logger *slog.Logger @@ -44,6 +47,7 @@ func NewApp( betSvc *bet.Service, walletSvc *wallet.Service, transactionSvc *transaction.Service, + branchSvc *branch.Service, notidicationStore notificationservice.NotificationStore, ) *App { app := fiber.New(fiber.Config{ @@ -52,6 +56,13 @@ func NewApp( JSONEncoder: sonic.Marshal, JSONDecoder: sonic.Unmarshal, }) + + app.Use(cors.New(cors.Config{ + AllowOrigins: "http://localhost:5173", // Specify your frontend's origin + AllowMethods: "GET,POST,PUT,DELETE", // Specify the allowed HTTP methods + AllowHeaders: "Content-Type,Authorization", // Specify the allowed headers + })) + s := &App{ fiber: app, port: port, @@ -64,6 +75,7 @@ func NewApp( betSvc: betSvc, walletSvc: walletSvc, transactionSvc: transactionSvc, + branchSvc: branchSvc, NotidicationStore: notidicationStore, Logger: logger, } diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 50c4a0a..e1af815 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -6,6 +6,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" "github.com/gofiber/fiber/v2" @@ -14,7 +15,6 @@ import ( type CreateBranchReq struct { Name string `json:"name" example:"4-kilo Branch"` Location string `json:"location" example:"Addis Ababa"` - WalletID int64 `json:"wallet_id" example:"1"` BranchManagerID int64 `json:"branch_manager_id" example:"1"` CompanyID int64 `json:"company_id" example:"1"` IsSelfOwned bool `json:"is_self_owned" example:"false"` @@ -85,12 +85,23 @@ func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes { } } -func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { +// CreateBranch godoc +// @Summary Create a branch +// @Description Creates a branch +// @Tags branch +// @Accept json +// @Produce json +// @Param createBranch body CreateBranchReq true "Creates branch" +// @Success 200 {object} BranchRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch [post] +func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { // Check if user is either branch manager / super main // role := string(c.Locals("role").(domain.Role)) - // if role != string(domain.RoleCustomer) { + // if role != string(domain.RoleAdmin) && role != string(domain.RoleSuperAdmin) && role != string(domain.RoleBranchManager) { // logger.Error("Unauthorized access", "role", role) // return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) // } @@ -110,10 +121,23 @@ func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus return nil } + // Create Branch Wallet + newWallet, err := walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + IsTransferable: true, + UserID: req.BranchManagerID, + }) + + if err != nil { + logger.Error("Create Branch Wallet failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create branch wallet", err, nil) + } + branch, err := branchSvc.CreateBranch(c.Context(), domain.CreateBranch{ Name: req.Name, Location: req.Location, - WalletID: req.WalletID, + WalletID: newWallet.ID, BranchManagerID: req.BranchManagerID, CompanyID: req.CompanyID, IsSelfOwned: req.IsSelfOwned, @@ -134,12 +158,23 @@ func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus } +// CreateSupportedOperation godoc +// @Summary Create a supported operation +// @Description Creates a supported operation +// @Tags branch +// @Accept json +// @Produce json +// @Param createSupportedOperation body CreateSupportedOperationReq true "Creates supported operation" +// @Success 200 {object} SupportedOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /supportedOperation [post] func CreateSupportedOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req CreateSupportedOperationReq if err := c.BodyParser(&req); err != nil { - logger.Error("CreateBranchReq failed", "error", err) + logger.Error("CreateSupportedOperationReq failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) @@ -171,6 +206,17 @@ func CreateSupportedOperation(logger *slog.Logger, branchSvc *branch.Service, va } } +// CreateBranchOperation godoc +// @Summary Create a operation +// @Description Creates a operation +// @Tags branch +// @Accept json +// @Produce json +// @Param createBranchOperation body CreateBranchOperationReq true "Creates operation" +// @Success 200 {object} BranchOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /operation [post] func CreateBranchOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req CreateBranchOperationReq @@ -202,6 +248,17 @@ func CreateBranchOperation(logger *slog.Logger, branchSvc *branch.Service, valid } } +// GetBranchByID godoc +// @Summary Gets branch by id +// @Description Gets a single branch by id +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Success 200 {object} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [get] func GetBranchByID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { branchID := c.Params("id") @@ -225,9 +282,21 @@ func GetBranchByID(logger *slog.Logger, branchSvc *branch.Service, validator *cu } } -// /user/:id/branch +// GetBranchByManagerID godoc +// @Summary Gets branches by manager id +// @Description Gets a branches by manager id +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /manager/{id}/branch [get] func GetBranchByManagerID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { + + // TODO: Restrict any who isn't branch manager or higher userID := c.Params("id") id, err := strconv.ParseInt(userID, 10, 64) if err != nil { @@ -249,7 +318,17 @@ func GetBranchByManagerID(logger *slog.Logger, branchSvc *branch.Service, valida } } -// /company/:id/branch +// GetBranchByCompanyID godoc +// @Summary Gets branches by company id +// @Description Gets branches by company id +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id}/branch [get] func GetBranchByCompanyID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { companyID := c.Params("id") @@ -273,10 +352,19 @@ func GetBranchByCompanyID(logger *slog.Logger, branchSvc *branch.Service, valida } } +// GetAllBranches godoc +// @Summary Gets all branches +// @Description Gets all branches +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch [get] func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { branches, err := branchSvc.GetAllBranches(c.Context()) - if err != nil { logger.Error("Failed to get branches", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) @@ -291,6 +379,17 @@ func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *c } } +// GetBranchOperations godoc +// @Summary Gets branch operations +// @Description Gets branch operations +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Success 200 {array} BranchOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/operation [get] func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { @@ -312,7 +411,7 @@ func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validat for _, branch := range operations { result = append(result, BranchOperationRes{ - Name: branch.OperationName, + Name: branch.OperationName, Description: branch.OperationDescription, }) } @@ -321,6 +420,18 @@ func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validat } } +// UpdateBranch godoc +// @Summary Updates a branch +// @Description Updates a branch +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Param updateBranch body CreateBranchReq true "Update Branch" +// @Success 200 {object} BranchRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [put] func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { branchID := c.Params("id") @@ -333,7 +444,7 @@ func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus var req CreateBranchReq if err := c.BodyParser(&req); err != nil { - logger.Error("CreateBetReq failed", "error", err) + logger.Error("CreateBranchReq failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) @@ -344,10 +455,9 @@ func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus return nil } - branch, err := branchSvc.UpdateBranch(c.Context(), id, domain.CreateBranch{ + branch, err := branchSvc.UpdateBranch(c.Context(), id, domain.UpdateBranch{ Name: req.Name, Location: req.Location, - WalletID: req.WalletID, BranchManagerID: req.BranchManagerID, CompanyID: req.CompanyID, IsSelfOwned: req.IsSelfOwned, @@ -365,6 +475,17 @@ func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus } } +// DeleteBranch godoc +// @Summary Delete the branch +// @Description Delete the branch +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID"" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [delete] func DeleteBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { @@ -388,6 +509,18 @@ func DeleteBranch(logger *slog.Logger, branchSvc *branch.Service, validator *cus } } +// DeleteBranchOperation godoc +// @Summary Delete the branch operation +// @Description Delete the branch operation +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Param opID path int true "Branch Operation ID" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/operation/{opID} [delete] func DeleteBranchOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { branchID := c.Params("id") diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index 1d143c7..0a93bf0 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -6,6 +6,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" "github.com/gofiber/fiber/v2" @@ -127,21 +128,51 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction [get] -func GetAllTransactions(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { +func GetAllTransactions( + logger *slog.Logger, + transactionSvc *transaction.Service, + userSvc *user.Service, + validator *customvalidator.CustomValidator, +) fiber.Handler { return func(c *fiber.Ctx) error { - transactions, err := transactionSvc.GetAllTransactions(c.Context()) + // Get user_id from middleware + userID := c.Locals("user_id").(int64) + + // Fetch user details + user, err := userSvc.GetUserByID(c.Context(), userID) + if err != nil { + logger.Error("Failed to fetch user details", "user_id", userID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve user details", err, nil) + } + + var transactions []domain.Transaction + + // Check user role and fetch transactions accordingly + switch user.Role { + case domain.RoleAdmin: + // Admin can fetch all transactions + transactions, err = transactionSvc.GetAllTransactions(c.Context()) + case domain.RoleBranchManager, domain.RoleCashier: + // Branch Manager or Cashier can fetch transactions for their branch + // transactions, err = transactionSvc.GetTransactionByBranch(c.Context(), user.BranchID) + transactions, err = transactionSvc.GetAllTransactions(c.Context()) + default: + // Unauthorized role + return response.WriteJSON(c, fiber.StatusForbidden, "Unauthorized", nil, nil) + } if err != nil { - logger.Error("Failed to get transaction", "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil) + logger.Error("Failed to get transactions", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) } + // Convert transactions to response format var res []TransactionRes = make([]TransactionRes, len(transactions)) - for _, transaction := range transactions { - res = append(res, convertTransaction(transaction)) + for i, transaction := range transactions { + res[i] = convertTransaction(transaction) } - return response.WriteJSON(c, fiber.StatusOK, "All Transactions Retrieved", res, nil) + return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) } } diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go new file mode 100644 index 0000000..c8635ee --- /dev/null +++ b/internal/web_server/handlers/transfer_handler.go @@ -0,0 +1,116 @@ +package handlers + +import ( + "log/slog" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "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" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type TransferRes 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"` +} + + +func convertTransfer(transfer domain.Transfer) TransferRes { + var senderWalletID *int64 + if transfer.SenderWalletID.Valid { + senderWalletID = &transfer.SenderWalletID.Value + } + + var cashierID *int64 + if transfer.CashierID.Valid { + cashierID = &transfer.CashierID.Value + } + + return TransferRes{ + ID: transfer.ID, + Amount: transfer.Amount.Float64(), + Verified: transfer.Verified, + Type: string(transfer.Type), + PaymentMethod: string(transfer.PaymentMethod), + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: senderWalletID, + CashierID: cashierID, + CreatedAt: transfer.CreatedAt, + UpdatedAt: transfer.UpdatedAt, + } +} + +type CreateTransferReq struct { + receiverID int64 + amount float64 + paymentMethod string +} + +// TransferToWallet godoc +// @Summary Create a transfer to wallet +// @Description Create a transfer to wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param transferToWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/wallet [post] +func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // 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) + + 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) + } + + 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.TransferToWallet(c.Context(), branchWallet.ID, req.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) + 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 49412cc..7056c0c 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -13,14 +13,57 @@ import ( ) type WalletRes struct { - ID int64 `json:"id" example:"1"` - Balance float32 `json:"amount" example:"100.0"` - IsWithdraw bool `json:"is_withdraw" example:"true"` - IsBettable bool `json:"is_bettable" example:"true"` - IsActive bool `json:"is_active" example:"true"` - UserID int64 `json:"user_id" example:"1"` - UpdatedAt time.Time `json:"updated_at"` - CreatedAt time.Time `json:"created_at"` + ID int64 `json:"id" example:"1"` + Balance float32 `json:"amount" example:"100.0"` + IsWithdraw bool `json:"is_withdraw" example:"true"` + IsBettable bool `json:"is_bettable" example:"true"` + IsTransferable bool `json:"is_transferable" example:"true"` + IsActive bool `json:"is_active" example:"true"` + UserID int64 `json:"user_id" example:"1"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func convertWallet(wallet domain.Wallet) WalletRes { + return WalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + } +} + +type CustomerWalletRes struct { + ID int64 `json:"id" example:"1"` + RegularID int64 `json:"regular_id" example:"1"` + RegularBalance float32 `json:"regular_balance" example:"100.0"` + StaticID int64 `json:"static_id" example:"1"` + StaticBalance float32 `json:"static_balance" example:"100.0"` + CustomerID int64 `json:"customer_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + RegularUpdatedAt time.Time `json:"regular_updated_at"` + StaticUpdatedAt time.Time `json:"static_updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes { + return CustomerWalletRes{ + ID: wallet.ID, + RegularID: wallet.RegularID, + RegularBalance: wallet.RegularBalance.Float64(), + StaticID: wallet.StaticID, + StaticBalance: wallet.StaticBalance.Float64(), + CustomerID: wallet.CustomerID, + CompanyID: wallet.CompanyID, + RegularUpdatedAt: wallet.RegularUpdatedAt, + StaticUpdatedAt: wallet.StaticUpdatedAt, + CreatedAt: wallet.CreatedAt, + } } // GetWalletByID godoc @@ -51,16 +94,7 @@ func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *cu return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil) } - res := WalletRes{ - ID: wallet.ID, - Balance: wallet.Balance.Float64(), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt, - CreatedAt: wallet.CreatedAt, - } + res := convertWallet(wallet) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) } @@ -87,16 +121,7 @@ func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *cu var res []WalletRes = make([]WalletRes, len(wallets)) for _, wallet := range wallets { - res = append(res, WalletRes{ - ID: wallet.ID, - Balance: wallet.Balance.Float64(), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt, - CreatedAt: wallet.CreatedAt, - }) + res = append(res, convertWallet(wallet)) } return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) @@ -148,19 +173,6 @@ func UpdateWalletActive(logger *slog.Logger, walletSvc *wallet.Service, validato } } -type CustomerWalletRes struct { - ID int64 `json:"id" example:"1"` - RegularID int64 `json:"regular_id" example:"1"` - RegularBalance float32 `json:"regular_balance" example:"100.0"` - StaticID int64 `json:"static_id" example:"1"` - StaticBalance float32 `json:"static_balance" example:"100.0"` - CustomerID int64 `json:"customer_id" example:"1"` - CompanyID int64 `json:"company_id" example:"1"` - RegularUpdatedAt time.Time `json:"regular_updated_at"` - StaticUpdatedAt time.Time `json:"static_updated_at"` - CreatedAt time.Time `json:"created_at"` -} - // GetCustomerWallet godoc // @Summary Get customer wallet // @Description Retrieve customer wallet details @@ -177,7 +189,7 @@ func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator return func(c *fiber.Ctx) error { userId := c.Locals("user_id").(int64) - role := string(c.Locals("role").(domain.Role)) + // role := string(c.Locals("role").(domain.Role)) vendorID, err := strconv.ParseInt(c.Get("vendor_id"), 10, 64) if err != nil { @@ -185,27 +197,16 @@ func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator } logger.Info("Company ID: " + strconv.FormatInt(vendorID, 10)) - if role != string(domain.RoleCustomer) { - logger.Error("Unauthorized access", "userId", userId, "role", role) - return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) - } + // if role != string(domain.RoleCustomer) { + // logger.Error("Unauthorized access", "userId", userId, "role", role) + // return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + // } wallet, err := walletSvc.GetCustomerWallet(c.Context(), userId, vendorID) if err != nil { logger.Error("Failed to get customer wallet", "userId", userId, "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil) } - res := CustomerWalletRes{ - ID: wallet.ID, - RegularID: wallet.RegularID, - RegularBalance: wallet.RegularBalance.Float64(), - StaticID: wallet.StaticID, - StaticBalance: wallet.StaticBalance.Float64(), - CustomerID: wallet.CustomerID, - CompanyID: wallet.CompanyID, - RegularUpdatedAt: wallet.RegularUpdatedAt, - StaticUpdatedAt: wallet.StaticUpdatedAt, - CreatedAt: wallet.CreatedAt, - } + res := convertCustomerWallet(wallet) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) } diff --git a/internal/web_server/middleware.go b/internal/web_server/middleware.go index 4f337fb..46e248b 100644 --- a/internal/web_server/middleware.go +++ b/internal/web_server/middleware.go @@ -4,6 +4,7 @@ import ( "errors" "strings" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" "github.com/gofiber/fiber/v2" ) @@ -39,5 +40,10 @@ func (a *App) authMiddleware(c *fiber.Ctx) error { c.Locals("user_id", claim.UserId) c.Locals("role", claim.Role) c.Locals("refresh_token", refreshToken) + + if claim.Role != domain.RoleCustomer { + // TODO: Add branch id here from the user + // c.Locals("branch_id", claim.) + } return c.Next() } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 204b3c5..5bb2b70 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -42,9 +42,26 @@ func (a *App) initAppRoutes() { a.fiber.Get("/user/wallet", a.authMiddleware, handlers.GetCustomerWallet(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/manager/:id/branch", handlers.GetBranchByManagerID(a.logger, a.branchSvc, a.validator)) + + a.fiber.Get("/company/:id/branch", handlers.GetBranchByCompanyID(a.logger, a.branchSvc, a.validator)) + // Swagger a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) + // Branch + 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.Put("/branch/:id", handlers.UpdateBranch(a.logger, a.branchSvc, a.validator)) + a.fiber.Delete("/branch/:id", handlers.DeleteBranch(a.logger, a.branchSvc, a.validator)) + + // Branch Operation + a.fiber.Post("/supportedOperation", handlers.CreateSupportedOperation(a.logger, a.branchSvc, a.validator)) + a.fiber.Post("/operation", handlers.CreateBranchOperation(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/branch/:id/operation", handlers.GetBranchOperations(a.logger, a.branchSvc, a.validator)) + a.fiber.Delete("/branch/:id/operation/:opID", handlers.DeleteBranchOperation(a.logger, a.branchSvc, a.validator)) + // Ticket a.fiber.Post("/ticket", handlers.CreateTicket(a.logger, a.ticketSvc, a.validator)) a.fiber.Get("/ticket", handlers.GetAllTickets(a.logger, a.ticketSvc, a.validator)) @@ -62,9 +79,13 @@ func (a *App) initAppRoutes() { 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)) - // Transactions /transactions + // Transfer + // /transfer/wallet - transfer from one wallet to another wallet + a.fiber.Post("/transfer/wallet", handlers.TransferToWallet(a.logger, a.walletSvc, a.branchSvc, a.validator)) + + // Transactions a.fiber.Post("/transaction", handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator)) - a.fiber.Get("/transaction", handlers.GetAllTransactions(a.logger, a.transactionSvc, a.validator)) + a.fiber.Get("/transaction", handlers.GetAllTransactions(a.logger, a.transactionSvc, a.userSvc, a.validator)) a.fiber.Get("/transaction/:id", handlers.GetTransactionByID(a.logger, a.transactionSvc, a.validator)) a.fiber.Patch("/transaction/:id", handlers.UpdateTransactionVerified(a.logger, a.transactionSvc, a.validator))