diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 27ae8b1..91e589f 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS users ( -- suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended suspended BOOLEAN NOT NULL DEFAULT FALSE, - CHECK (email IS NOT NULL OR phone_number IS NOT NULL) + CHECK (email IS NOT NULL OR phone_number IS NOT NULL) ); CREATE TABLE refresh_tokens ( id BIGSERIAL PRIMARY KEY, @@ -64,13 +64,15 @@ CREATE TABLE IF NOT EXISTS tickets ( -- CREATE TABLE IF NOT EXISTS bet_outcomes ( -- id BIGSERIAL PRIMARY KEY, -- bet_id BIGINT NOT NULL, --- outcome_id BIGINT NOT NULL, +-- event_id bigint not null, +-- odd_id BIGINT NOT NULL, -- ); -- CREATE TABLE IF NOT EXISTS ticket_outcomes ( -- id BIGSERIAL PRIMARY KEY, -- ticket_id BIGINT NOT NULL, --- outcome_id BIGINT NOT NULL, +-- event_id bigint not null, +-- odd_id BIGINT NOT NULL, -- ); CREATE TABLE IF NOT EXISTS wallets ( @@ -186,6 +188,25 @@ INSERT INTO users ( FALSE ); +INSERT INTO users ( + first_name, last_name, email, phone_number, password, role, + email_verified, phone_verified, created_at, updated_at, + suspended_at, suspended +) VALUES ( + 'Samuel', + 'Tariku', + 'cybersamt@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'cashier', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + NULL, + FALSE +); + INSERT INTO supported_operations ( name, description diff --git a/db/query/branch.sql b/db/query/branch.sql index 736cf08..97b374e 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -20,6 +20,9 @@ SELECT * FROM branch_details WHERE company_id = $1; -- name: GetBranchByManagerID :many SELECT * FROM branch_details WHERE branch_manager_id = $1; +-- name: SearchBranchByName :many +SELECT * FROM branch_details WHERE name ILIKE '%' || $1 || '%'; + -- name: GetAllSupportedOperations :many SELECT * FROM supported_operations; diff --git a/db/query/user.sql b/db/query/user.sql index 04cbf84..8e127af 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -13,6 +13,13 @@ WHERE id = $1; SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at FROM users; +-- name: SearchUserByNameOrPhone :many +SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +FROM users +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' + OR phone_number LIKE '%' || $1 || '%'; + -- name: UpdateUser :exec UPDATE users SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = $6 diff --git a/docs/docs.go b/docs/docs.go index 8cbb6b4..cd7719d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -857,6 +857,42 @@ const docTemplate = `{ } }, "/supportedOperation": { + "get": { + "description": "Gets all supported operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all supported operations", + "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 supported operation", "consumes": [ @@ -1429,6 +1465,52 @@ const docTemplate = `{ } } }, + "/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/sendRegisterCode": { "post": { "description": "Send register code", @@ -1983,6 +2065,12 @@ const docTemplate = `{ "name": { "type": "string", "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } } } }, @@ -2081,7 +2169,21 @@ const docTemplate = `{ } }, "handlers.CreateTransferReq": { - "type": "object" + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "payment_method": { + "type": "string", + "example": "cash" + }, + "receiver_id": { + "type": "integer", + "example": 1 + } + } }, "handlers.CustomerWalletRes": { "type": "object", @@ -2202,6 +2304,14 @@ const docTemplate = `{ } } }, + "handlers.SearchUserByNameOrPhoneReq": { + "type": "object", + "properties": { + "searchString": { + "type": "string" + } + } + }, "handlers.SupportedOperationRes": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 21d28dc..7ecbb4f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -849,6 +849,42 @@ } }, "/supportedOperation": { + "get": { + "description": "Gets all supported operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all supported operations", + "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 supported operation", "consumes": [ @@ -1421,6 +1457,52 @@ } } }, + "/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/sendRegisterCode": { "post": { "description": "Send register code", @@ -1975,6 +2057,12 @@ "name": { "type": "string", "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } } } }, @@ -2073,7 +2161,21 @@ } }, "handlers.CreateTransferReq": { - "type": "object" + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "payment_method": { + "type": "string", + "example": "cash" + }, + "receiver_id": { + "type": "integer", + "example": 1 + } + } }, "handlers.CustomerWalletRes": { "type": "object", @@ -2194,6 +2296,14 @@ } } }, + "handlers.SearchUserByNameOrPhoneReq": { + "type": "object", + "properties": { + "searchString": { + "type": "string" + } + } + }, "handlers.SupportedOperationRes": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e6031ac..69e4307 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -199,6 +199,10 @@ definitions: name: example: 4-kilo Branch type: string + operations: + items: + type: integer + type: array type: object handlers.CreateSupportedOperationReq: properties: @@ -265,6 +269,16 @@ definitions: type: string type: object handlers.CreateTransferReq: + properties: + amount: + example: 100 + type: number + payment_method: + example: cash + type: string + receiver_id: + example: 1 + type: integer type: object handlers.CustomerWalletRes: properties: @@ -350,6 +364,11 @@ definitions: phoneNumber: type: string type: object + handlers.SearchUserByNameOrPhoneReq: + properties: + searchString: + type: string + type: object handlers.SupportedOperationRes: properties: description: @@ -1135,6 +1154,30 @@ paths: tags: - branch /supportedOperation: + get: + consumes: + - application/json + description: Gets all supported operations + 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 supported operations + tags: + - branch post: consumes: - application/json @@ -1509,6 +1552,36 @@ paths: summary: Reset password tags: - user + /user/search: + post: + consumes: + - application/json + description: Search for user using name or phone + parameters: + - description: Search for using his name or phone + in: body + name: searchUserByNameOrPhone + required: true + schema: + $ref: '#/definitions/handlers.SearchUserByNameOrPhoneReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.UserProfileRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Search for user using name or phone + tags: + - user /user/sendRegisterCode: post: consumes: diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index 5197e58..8d34fb3 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -309,6 +309,42 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge return items, nil } +const SearchBranchByName = `-- name: SearchBranchByName :many +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number FROM branch_details WHERE name ILIKE '%' || $1 || '%' +` + +func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, SearchBranchByName, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BranchDetail + for rows.Next() { + var i BranchDetail + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const UpdateBranch = `-- name: UpdateBranch :one UPDATE branches SET name = $1, location = $2, branch_manager_id = $3, company_id = $4, is_self_owned = $5 WHERE id = $6 RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at ` diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index 39f0a5c..bce776c 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -256,6 +256,58 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) ( return i, err } +const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many +SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +FROM users +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' + OR phone_number LIKE '%' || $1 || '%' +` + +type SearchUserByNameOrPhoneRow struct { + ID int64 + FirstName string + LastName string + Email pgtype.Text + PhoneNumber pgtype.Text + Role string + EmailVerified bool + PhoneVerified bool + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.Text) ([]SearchUserByNameOrPhoneRow, error) { + rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SearchUserByNameOrPhoneRow + for rows.Next() { + var i SearchUserByNameOrPhoneRow + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.EmailVerified, + &i.PhoneVerified, + &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 UpdatePassword = `-- name: UpdatePassword :exec UPDATE users SET password = $1, updated_at = $4 diff --git a/internal/domain/user.go b/internal/domain/user.go index ea44cc8..b4d2fa4 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -33,7 +33,7 @@ type RegisterUserReq struct { Email string PhoneNumber string Password string - //Role string + Role string Otp string ReferalCode string // diff --git a/internal/repository/bet.go b/internal/repository/bet.go index c4d0362..2ddef36 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -73,7 +73,7 @@ func (s *Store) GetAllBets(ctx context.Context) ([]domain.Bet, error) { return nil, err } - var result []domain.Bet = make([]domain.Bet, len(bets)) + var result []domain.Bet = make([]domain.Bet, 0, len(bets)) for _, bet := range bets { result = append(result, convertDBBet(bet)) } diff --git a/internal/repository/branch.go b/internal/repository/branch.go index a37493a..6268615 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -5,6 +5,7 @@ import ( dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" ) func convertCreateBranch(branch domain.CreateBranch) dbgen.CreateBranchParams { @@ -54,29 +55,6 @@ func (s *Store) CreateBranch(ctx context.Context, branch domain.CreateBranch) (d return convertDBBranch(dbBranch), nil } -func (s *Store) CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) { - dbSupportedOperation, err := s.queries.CreateSupportedOperation(ctx, dbgen.CreateSupportedOperationParams{ - Name: supportedOperation.Name, - Description: supportedOperation.Description, - }) - if err != nil { - return domain.SupportedOperation{}, err - } - return domain.SupportedOperation{ - ID: dbSupportedOperation.ID, - Name: dbSupportedOperation.Name, - Description: dbSupportedOperation.Description, - }, nil -} - -func (s *Store) CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error { - _, err := s.queries.CreateBranchOperation(ctx, dbgen.CreateBranchOperationParams{ - BranchID: branchOperation.BranchID, - OperationID: branchOperation.OperationID, - }) - return err -} - func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { dbBranch, err := s.queries.GetBranchByID(ctx, id) if err != nil { @@ -108,22 +86,6 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do return branches, nil } -func (s *Store) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) { - dbBranchOperations, err := s.queries.GetBranchOperations(ctx, branchID) - if err != nil { - return nil, err - } - var branchOperations []domain.BranchOperation = make([]domain.BranchOperation, 0, len(dbBranchOperations)) - for _, dbBranchOperation := range dbBranchOperations { - branchOperations = append(branchOperations, domain.BranchOperation{ - ID: dbBranchOperation.ID, - OperationName: dbBranchOperation.Name, - OperationDescription: dbBranchOperation.Description, - }) - } - return branchOperations, nil -} - func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { dbBranches, err := s.queries.GetAllBranches(ctx) if err != nil { @@ -136,6 +98,19 @@ func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, erro return branches, nil } +func (s *Store) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.SearchBranchByName(ctx, pgtype.Text{String: name, Valid: true}) + if err != nil { + return nil, err + } + + var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) + for _, dbBranch := range dbBranches { + branches = append(branches, convertDBBranchDetail(dbBranch)) + } + return branches, nil +} + 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, @@ -154,6 +129,65 @@ func (s *Store) DeleteBranch(ctx context.Context, id int64) error { return s.queries.DeleteBranch(ctx, id) } +// Branch Operations + +func (s *Store) CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error { + _, err := s.queries.CreateBranchOperation(ctx, dbgen.CreateBranchOperationParams{ + BranchID: branchOperation.BranchID, + OperationID: branchOperation.OperationID, + }) + return err +} + +func (s *Store) CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) { + dbSupportedOperation, err := s.queries.CreateSupportedOperation(ctx, dbgen.CreateSupportedOperationParams{ + Name: supportedOperation.Name, + Description: supportedOperation.Description, + }) + if err != nil { + return domain.SupportedOperation{}, err + } + return domain.SupportedOperation{ + ID: dbSupportedOperation.ID, + Name: dbSupportedOperation.Name, + Description: dbSupportedOperation.Description, + }, nil +} + +func (s *Store) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { + dbOperations, err := s.queries.GetAllSupportedOperations(ctx) + if err != nil { + return nil, err + } + + var operations []domain.SupportedOperation = make([]domain.SupportedOperation, 0, len(dbOperations)) + for _, dbOperation := range dbOperations { + operations = append(operations, domain.SupportedOperation{ + ID: dbOperation.ID, + Name: dbOperation.Name, + Description: dbOperation.Description, + }) + } + return operations, nil + +} + +func (s *Store) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) { + dbBranchOperations, err := s.queries.GetBranchOperations(ctx, branchID) + if err != nil { + return nil, err + } + var branchOperations []domain.BranchOperation = make([]domain.BranchOperation, 0, len(dbBranchOperations)) + for _, dbBranchOperation := range dbBranchOperations { + branchOperations = append(branchOperations, domain.BranchOperation{ + ID: dbBranchOperation.ID, + OperationName: dbBranchOperation.Name, + OperationDescription: dbBranchOperation.Description, + }) + } + return branchOperations, nil +} + func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { err := s.queries.DeleteBranchOperation(ctx, dbgen.DeleteBranchOperationParams{ BranchID: branchID, diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index d6918d3..b7945c3 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -51,7 +51,7 @@ func (s *Store) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { return nil, err } - var result []domain.Ticket = make([]domain.Ticket, len(tickets)) + var result []domain.Ticket = make([]domain.Ticket, 0, len(tickets)) for _, ticket := range tickets { result = append(result, convertDBTicket(ticket)) } diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go index f0faad3..e7f3e4f 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/transaction.go @@ -66,7 +66,7 @@ func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, e return nil, err } - var result []domain.Transaction = make([]domain.Transaction, len(transaction)) + var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) for _, ticket := range transaction { result = append(result, convertDBTransaction(ticket)) } @@ -79,7 +79,7 @@ func (s *Store) GetTransactionByBranch(ctx context.Context, id int64) ([]domain. return nil, err } - var result []domain.Transaction = make([]domain.Transaction, len(transaction)) + var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) for _, ticket := range transaction { result = append(result, convertDBTransaction(ticket)) } diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go index 70884f3..7ee876e 100644 --- a/internal/repository/transfer.go +++ b/internal/repository/transfer.go @@ -57,7 +57,7 @@ func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) if err != nil { return nil, err } - var result []domain.Transfer = make([]domain.Transfer, len(transfers)) + var result []domain.Transfer = make([]domain.Transfer, 0, len(transfers)) for _, transfer := range transfers { result = append(result, convertDBTransfer(transfer)) @@ -70,7 +70,7 @@ func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]dom return nil, err } - var result []domain.Transfer = make([]domain.Transfer, len(transfers)) + var result []domain.Transfer = make([]domain.Transfer, 0, len(transfers)) for _, transfer := range transfers { result = append(result, convertDBTransfer(transfer)) diff --git a/internal/repository/user.go b/internal/repository/user.go index 2177b1e..98814f0 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -100,6 +100,30 @@ func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) { } return userList, nil } + +func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) { + users, err := s.queries.SearchUserByNameOrPhone(ctx, pgtype.Text{ + String: searchString, + Valid: true, + }) + if err != nil { + return nil, err + } + + userList := make([]domain.User, 0, len(users)) + for _, user := range users { + userList = append(userList, domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + }) + } + return userList, nil +} + func (s *Store) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ // ID: user.ID, diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 7126fc4..97a87e5 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -95,7 +95,7 @@ func (s *Store) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { return nil, err } - var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + var result []domain.Wallet = make([]domain.Wallet, 0, len(wallets)) for _, wallet := range wallets { result = append(result, convertDBWallet(wallet)) @@ -109,7 +109,7 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa return nil, err } - var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + var result []domain.Wallet = make([]domain.Wallet, 0, len(wallets)) for _, wallet := range wallets { result = append(result, convertDBWallet(wallet)) diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go index 633fa31..e2f2ec1 100644 --- a/internal/services/branch/port.go +++ b/internal/services/branch/port.go @@ -15,6 +15,8 @@ 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) + GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) + SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) DeleteBranch(ctx context.Context, id int64) error DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go index 49a87ff..83d97e2 100644 --- a/internal/services/branch/service.go +++ b/internal/services/branch/service.go @@ -40,6 +40,14 @@ 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) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { + return s.branchStore.GetAllSupportedOperations(ctx) +} + +func (s *Service) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { + return s.branchStore.SearchBranchByName(ctx, name) +} func (s *Service) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) { return s.branchStore.UpdateBranch(ctx, id, branch) } diff --git a/internal/services/user/port.go b/internal/services/user/port.go index aaf502d..bcfdd5a 100644 --- a/internal/services/user/port.go +++ b/internal/services/user/port.go @@ -15,7 +15,7 @@ type UserStore interface { CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) GetUserByEmail(ctx context.Context, email string) (domain.User, error) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) - // + SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error // identifier verified email or phone } type SmsGateway interface { diff --git a/internal/services/user/register.go b/internal/services/user/register.go index 7966254..dcefa99 100644 --- a/internal/services/user/register.go +++ b/internal/services/user/register.go @@ -65,7 +65,7 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU Email: registerReq.Email, PhoneNumber: registerReq.PhoneNumber, Password: hashedPassword, - Role: "user", + Role: domain.RoleCustomer, EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail, PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms, } diff --git a/internal/services/user/user.go b/internal/services/user/user.go index 5b65b94..5c141c7 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -6,6 +6,14 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) + + + +func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) { + // Search user + return s.userStore.SearchUserByNameOrPhone(ctx, searchString) + +} func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { // update user return s.userStore.UpdateUser(ctx, user) diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 5f47b7c..5c9678f 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -62,7 +62,9 @@ func convertBet(bet domain.Bet) BetRes { func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { // TODO if user is customer, get id from the token then get the wallet id from there + // TODO: If user is a cashier, check the token, and find the role and get the branch id from there. Reduce amount from the branch wallet + var isShopBet bool = true var branchID int64 = 1 @@ -134,7 +136,7 @@ func GetAllBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil) } - var res []BetRes = make([]BetRes, len(bets)) + var res []BetRes = make([]BetRes, 0, len(bets)) for _, bet := range bets { res = append(res, convertBet(bet)) } diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index e1af815..8e0552e 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -13,11 +13,12 @@ import ( ) type CreateBranchReq struct { - Name string `json:"name" example:"4-kilo Branch"` - Location string `json:"location" example:"Addis Ababa"` - BranchManagerID int64 `json:"branch_manager_id" example:"1"` - CompanyID int64 `json:"company_id" example:"1"` - IsSelfOwned bool `json:"is_self_owned" example:"false"` + Name string `json:"name" example:"4-kilo Branch"` + Location string `json:"location" example:"Addis Ababa"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` + Operations []int64 `json:"operations"` } type CreateSupportedOperationReq struct { @@ -310,7 +311,7 @@ func GetBranchByManagerID(logger *slog.Logger, branchSvc *branch.Service, valida logger.Error("Failed to get branches", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) } - var result []BranchDetailRes = make([]BranchDetailRes, len(branches)) + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) for _, branch := range branches { result = append(result, convertBranchDetail(branch)) } @@ -344,7 +345,7 @@ func GetBranchByCompanyID(logger *slog.Logger, branchSvc *branch.Service, valida return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) } - var result []BranchDetailRes = make([]BranchDetailRes, len(branches)) + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) for _, branch := range branches { result = append(result, convertBranchDetail(branch)) } @@ -370,7 +371,7 @@ func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *c return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) } - var result []BranchDetailRes = make([]BranchDetailRes, len(branches)) + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) for _, branch := range branches { result = append(result, convertBranchDetail(branch)) } @@ -379,6 +380,37 @@ func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *c } } +// GetAllSupportedOperations godoc +// @Summary Gets all supported operations +// @Description Gets all supported operations +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /supportedOperation [get] +func GetAllSupportedOperations(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + operations, err := branchSvc.GetAllSupportedOperations(c.Context()) + if err != nil { + logger.Error("Failed to get operations", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get operations", err, nil) + } + + var result []SupportedOperationRes = make([]SupportedOperationRes, 0, len(operations)) + for _, operation := range operations { + result = append(result, SupportedOperationRes{ + ID: operation.ID, + Name: operation.Name, + Description: operation.Description, + }) + } + return response.WriteJSON(c, fiber.StatusOK, "SupportedOperations for Company retrieved", result, nil) + + } +} + // GetBranchOperations godoc // @Summary Gets branch operations // @Description Gets branch operations @@ -407,7 +439,7 @@ func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validat return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve operation", err, nil) } - var result []BranchOperationRes = make([]BranchOperationRes, len(operations)) + var result []BranchOperationRes = make([]BranchOperationRes, 0, len(operations)) for _, branch := range operations { result = append(result, BranchOperationRes{ diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index 68bba4b..05339d4 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -136,7 +136,7 @@ func GetAllTickets(logger *slog.Logger, ticketSvc *ticket.Service, return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve tickets", err, nil) } - var res []TicketRes = make([]TicketRes, len(tickets)) + var res []TicketRes = make([]TicketRes, 0, len(tickets)) for _, ticket := range tickets { res = append(res, TicketRes{ diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index 0a93bf0..77f80c9 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -167,7 +167,7 @@ func GetAllTransactions( } // Convert transactions to response format - var res []TransactionRes = make([]TransactionRes, len(transactions)) + var res []TransactionRes = make([]TransactionRes, 0, len(transactions)) for i, transaction := range transactions { res[i] = convertTransaction(transaction) } diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go index c8635ee..4ff7810 100644 --- a/internal/web_server/handlers/transfer_handler.go +++ b/internal/web_server/handlers/transfer_handler.go @@ -19,42 +19,41 @@ type TransferRes struct { 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"` + 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 senderWalletID *int64 + if transfer.SenderWalletID.Valid { + senderWalletID = &transfer.SenderWalletID.Value + } - var cashierID *int64 - if transfer.CashierID.Valid { - cashierID = &transfer.CashierID.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, - } + 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 + ReceiverID int64 `json:"receiver_id" example:"1"` + Amount float64 `json:"amount" example:"100.0"` + PaymentMethod string `json:"payment_method" example:"cash"` } // TransferToWallet godoc @@ -101,7 +100,7 @@ func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc return nil } - transfer, err := walletSvc.TransferToWallet(c.Context(), branchWallet.ID, req.receiverID, domain.Currency(req.amount), domain.PaymentMethod(req.paymentMethod), domain.ValidInt64{Value: userID, Valid: true}) + transfer, err := walletSvc.TransferToWallet(c.Context(), 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) diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 665a1ee..a854e78 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -374,3 +374,61 @@ func getMedium(email, phoneNumber string) (domain.OtpMedium, error) { } return "", errors.New("both email and phone number are empty") } + +type SearchUserByNameOrPhoneReq struct { + SearchString string +} + +// SearchUserByNameOrPhone godoc +// @Summary Search for user using name or phone +// @Description Search for user using name or phone +// @Tags user +// @Accept json +// @Produce json +// @Param searchUserByNameOrPhone body SearchUserByNameOrPhoneReq true "Search for using his name or phone" +// @Success 200 {object} UserProfileRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /user/search [post] +func SearchUserByNameOrPhone(logger *slog.Logger, userSvc *user.Service, + validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req SearchUserByNameOrPhoneReq + if err := c.BodyParser(&req); err != nil { + logger.Error("SearchUserByNameOrPhone 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 + } + users, err := userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString) + if err != nil { + logger.Error("SearchUserByNameOrPhone failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) + for _, user := range users { + res = append(res, UserProfileRes{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Role: user.Role, + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + SuspendedAt: user.SuspendedAt, + Suspended: user.Suspended, + }) + } + return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil) + } +} diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index 7056c0c..de655f6 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -118,7 +118,7 @@ func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *cu logger.Error("Failed to get wallets", "error", err) } - var res []WalletRes = make([]WalletRes, len(wallets)) + var res []WalletRes = make([]WalletRes, 0, len(wallets)) for _, wallet := range wallets { res = append(res, convertWallet(wallet)) diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 5bb2b70..b25ed9d 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -41,6 +41,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc)) a.fiber.Get("/user/wallet", a.authMiddleware, handlers.GetCustomerWallet(a.logger, a.walletSvc, a.validator)) + a.fiber.Post("/user/search", handlers.SearchUserByNameOrPhone(a.logger, a.userSvc, a.validator)) a.fiber.Get("/manager/:id/branch", handlers.GetBranchByManagerID(a.logger, a.branchSvc, a.validator)) @@ -55,8 +56,10 @@ func (a *App) initAppRoutes() { 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/wallet // Branch Operation + a.fiber.Get("/supportedOperation", handlers.GetAllSupportedOperations(a.logger, a.branchSvc, a.validator)) 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)) @@ -81,13 +84,13 @@ func (a *App) initAppRoutes() { // 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)) + a.fiber.Post("/transfer/wallet", a.authMiddleware, 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.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)) + a.fiber.Post("/transaction", a.authMiddleware, handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator)) + a.fiber.Get("/transaction", a.authMiddleware, handlers.GetAllTransactions(a.logger, a.transactionSvc, a.userSvc, a.validator)) + a.fiber.Get("/transaction/:id", a.authMiddleware, handlers.GetTransactionByID(a.logger, a.transactionSvc, a.validator)) + a.fiber.Patch("/transaction/:id", a.authMiddleware, handlers.UpdateTransactionVerified(a.logger, a.transactionSvc, a.validator)) a.fiber.Get("/notifications/ws/connect/:recipientID", handler.ConnectSocket) a.fiber.Post("/notifications/mark-as-read", handler.MarkNotificationAsRead)