From 4d5c90ab05c7cd4bd07ba81f2b07bf835d6142ea Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Wed, 25 Jun 2025 22:47:06 +0300 Subject: [PATCH] feat: bet and branch filters, admin company, customer wallet --- db/migrations/000001_fortune.up.sql | 22 ++++- db/query/bet.sql | 30 ++++++- db/query/branch.sql | 17 ++++ db/query/wallet.sql | 19 ++--- gen/db/bet.sql.go | 80 +++++++++++++++-- gen/db/branch.sql.go | 49 +++++++++-- gen/db/models.go | 18 ++++ gen/db/wallet.sql.go | 77 +++++++++++------ internal/domain/bet.go | 11 ++- internal/domain/branch.go | 15 ++-- internal/domain/wallet.go | 12 +++ internal/repository/bet.go | 49 +++++++++-- internal/repository/branch.go | 18 ++++ internal/repository/wallet.go | 20 ++++- internal/services/bet/port.go | 4 +- internal/services/bet/service.go | 85 +++++++++++++++---- internal/services/result/service.go | 4 +- internal/services/wallet/port.go | 1 + internal/services/wallet/wallet.go | 5 +- internal/web_server/handlers/bet_handler.go | 49 +++++++++-- .../web_server/handlers/branch_handler.go | 59 ++++++++++++- .../web_server/handlers/company_handler.go | 27 ++++++ internal/web_server/handlers/mongoLogger.go | 3 +- internal/web_server/handlers/user.go | 8 +- .../web_server/handlers/wallet_handler.go | 48 +++++++++-- internal/web_server/routes.go | 2 + 26 files changed, 615 insertions(+), 117 deletions(-) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index b57d127..699ad8a 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -286,7 +286,8 @@ CREATE VIEW branch_details AS SELECT branches.*, CONCAT(users.first_name, ' ', users.last_name) AS manager_name, users.phone_number AS manager_phone_number, - wallets.balance + wallets.balance, + wallets.is_active AS wallet_is_active FROM branches LEFT JOIN users ON branches.branch_manager_id = users.id LEFT JOin wallets ON wallets.id = branches.wallet_id; @@ -307,6 +308,25 @@ SELECT tickets.*, FROM tickets LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id GROUP BY tickets.id; +CREATE VIEW customer_wallet_details AS +SELECT cw.id, + cw.customer_id, + rw.id AS regular_id, + rw.balance AS regular_balance, + sw.id AS static_id, + sw.balance AS static_balance, + rw.is_active as regular_is_active, + sw.is_active as static_is_active, + rw.updated_at as regular_updated_at, + sw.updated_at as static_updated_at, + cw.created_at, + users.first_name, + users.last_name, + users.phone_number +FROM customer_wallets cw + JOIN wallets rw ON cw.regular_wallet_id = rw.id + JOIN wallets sw ON cw.static_wallet_id = sw.id + JOIN users ON users.id = cw.customer_id; -- Foreign Keys ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email), diff --git a/db/query/bet.sql b/db/query/bet.sql index 0553e2d..8989ffe 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -63,6 +63,19 @@ wHERE ( AND ( is_shop_bet = sqlc.narg('is_shop_bet') OR sqlc.narg('is_shop_bet') IS NULL + ) + AND ( + full_name ILIKE '%' || sqlc.narg('query') || '%' + OR phone_number ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL ); -- name: GetBetByID :one SELECT * @@ -83,7 +96,13 @@ WHERE user_id = $1; -- name: GetBetOutcomeByEventID :many SELECT * FROM bet_outcomes -WHERE event_id = $1; +WHERE (event_id = $1) + AND ( + status = sqlc.narg('filter_status') + OR sqlc.narg('filter_status') IS NULL + OR status = sqlc.narg('filter_status_2') + OR sqlc.narg('filter_status_2') IS NULL + ); -- name: GetBetOutcomeByBetID :many SELECT * FROM bet_outcomes @@ -103,6 +122,11 @@ UPDATE bet_outcomes SET status = $1 WHERE id = $2 RETURNING *; +-- name: UpdateBetOutcomeStatusByBetID :one +UPDATE bet_outcomes +SET status = $1 +WHERE bet_id = $2 +RETURNING *; -- name: UpdateBetOutcomeStatusForEvent :many UPDATE bet_outcomes SEt status = $1 @@ -118,6 +142,4 @@ DELETE FROM bets WHERE id = $1; -- name: DeleteBetOutcome :exec DELETE FROM bet_outcomes -WHERE bet_id = $1; - - +WHERE bet_id = $1; \ No newline at end of file diff --git a/db/query/branch.sql b/db/query/branch.sql index 34070cc..34f22eb 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -31,6 +31,23 @@ WHERE ( AND ( is_active = sqlc.narg('is_active') OR sqlc.narg('is_active') IS NULL + ) + AND ( + branch_manager_id = sqlc.narg('branch_manager_id') + OR sqlc.narg('branch_manager_id') IS NULL + ) + AND ( + name ILIKE '%' || sqlc.narg('query') || '%' + OR location ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + created_at > sqlc.narg('created_before') + OR sqlc.narg('created_before') IS NULL + ) + AND ( + created_at < sqlc.narg('created_after') + OR sqlc.narg('created_after') IS NULL ); -- name: GetBranchByID :one SELECT * diff --git a/db/query/wallet.sql b/db/query/wallet.sql index e825653..a5f8482 100644 --- a/db/query/wallet.sql +++ b/db/query/wallet.sql @@ -26,20 +26,13 @@ WHERE id = $1; SELECT * FROM wallets WHERE user_id = $1; +-- name: GetAllCustomerWallet :many +SELECT * +FROM customer_wallet_details; -- name: GetCustomerWallet :one -SELECT cw.id, - cw.customer_id, - rw.id AS regular_id, - rw.balance AS regular_balance, - sw.id AS static_id, - sw.balance AS static_balance, - rw.updated_at as regular_updated_at, - sw.updated_at as static_updated_at, - cw.created_at -FROM customer_wallets cw - JOIN wallets rw ON cw.regular_wallet_id = rw.id - JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1; +SELECT * +FROM customer_wallet_details +WHERE customer_id = $1; -- name: GetAllBranchWallets :many SELECT wallets.id, wallets.balance, diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 52452f2..c5da84e 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -133,13 +133,29 @@ wHERE ( is_shop_bet = $4 OR $4 IS NULL ) + AND ( + full_name ILIKE '%' || $5 || '%' + OR phone_number ILIKE '%' || $5 || '%' + OR $5 IS NULL + ) + AND ( + created_at > $6 + OR $6 IS NULL + ) + AND ( + created_at < $7 + OR $7 IS NULL + ) ` type GetAllBetsParams struct { - BranchID pgtype.Int8 `json:"branch_id"` - CompanyID pgtype.Int8 `json:"company_id"` - UserID pgtype.Int8 `json:"user_id"` - IsShopBet pgtype.Bool `json:"is_shop_bet"` + BranchID pgtype.Int8 `json:"branch_id"` + CompanyID pgtype.Int8 `json:"company_id"` + UserID pgtype.Int8 `json:"user_id"` + IsShopBet pgtype.Bool `json:"is_shop_bet"` + Query pgtype.Text `json:"query"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` } func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) { @@ -148,6 +164,9 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi arg.CompanyID, arg.UserID, arg.IsShopBet, + arg.Query, + arg.CreatedBefore, + arg.CreatedAfter, ) if err != nil { return nil, err @@ -394,11 +413,23 @@ func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetO const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires FROM bet_outcomes -WHERE event_id = $1 +WHERE (event_id = $1) + AND ( + status = $2 + OR $2 IS NULL + OR status = $3 + OR $3 IS NULL + ) ` -func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]BetOutcome, error) { - rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID) +type GetBetOutcomeByEventIDParams struct { + EventID int64 `json:"event_id"` + FilterStatus pgtype.Int4 `json:"filter_status"` + FilterStatus2 pgtype.Int4 `json:"filter_status_2"` +} + +func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeByEventIDParams) ([]BetOutcome, error) { + rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, arg.EventID, arg.FilterStatus, arg.FilterStatus2) if err != nil { return nil, err } @@ -468,6 +499,41 @@ func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutco return i, err } +const UpdateBetOutcomeStatusByBetID = `-- name: UpdateBetOutcomeStatusByBetID :one +UPDATE bet_outcomes +SET status = $1 +WHERE bet_id = $2 +RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires +` + +type UpdateBetOutcomeStatusByBetIDParams struct { + Status int32 `json:"status"` + BetID int64 `json:"bet_id"` +} + +func (q *Queries) UpdateBetOutcomeStatusByBetID(ctx context.Context, arg UpdateBetOutcomeStatusByBetIDParams) (BetOutcome, error) { + row := q.db.QueryRow(ctx, UpdateBetOutcomeStatusByBetID, arg.Status, arg.BetID) + var i BetOutcome + err := row.Scan( + &i.ID, + &i.BetID, + &i.SportID, + &i.EventID, + &i.OddID, + &i.HomeTeamName, + &i.AwayTeamName, + &i.MarketID, + &i.MarketName, + &i.Odd, + &i.OddName, + &i.OddHeader, + &i.OddHandicap, + &i.Status, + &i.Expires, + ) + return i, err +} + const UpdateBetOutcomeStatusForEvent = `-- name: UpdateBetOutcomeStatusForEvent :many UPDATE bet_outcomes SEt status = $1 diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index 7e8a754..71e5257 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -155,7 +155,7 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe } const GetAllBranches = `-- name: GetAllBranches :many -SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance +SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE ( company_id = $1 @@ -165,15 +165,43 @@ WHERE ( is_active = $2 OR $2 IS NULL ) + AND ( + branch_manager_id = $3 + OR $3 IS NULL + ) + AND ( + name ILIKE '%' || $4 || '%' + OR location ILIKE '%' || $4 || '%' + OR $4 IS NULL + ) + AND ( + created_at > $5 + OR $5 IS NULL + ) + AND ( + created_at < $6 + OR $6 IS NULL + ) ` type GetAllBranchesParams struct { - CompanyID pgtype.Int8 `json:"company_id"` - IsActive pgtype.Bool `json:"is_active"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive pgtype.Bool `json:"is_active"` + BranchManagerID pgtype.Int8 `json:"branch_manager_id"` + Query pgtype.Text `json:"query"` + CreatedBefore pgtype.Timestamp `json:"created_before"` + CreatedAfter pgtype.Timestamp `json:"created_after"` } func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams) ([]BranchDetail, error) { - rows, err := q.db.Query(ctx, GetAllBranches, arg.CompanyID, arg.IsActive) + rows, err := q.db.Query(ctx, GetAllBranches, + arg.CompanyID, + arg.IsActive, + arg.BranchManagerID, + arg.Query, + arg.CreatedBefore, + arg.CreatedAfter, + ) if err != nil { return nil, err } @@ -195,6 +223,7 @@ func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams) &i.ManagerName, &i.ManagerPhoneNumber, &i.Balance, + &i.WalletIsActive, ); err != nil { return nil, err } @@ -257,7 +286,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch, } const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many -SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance +SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE company_id = $1 ` @@ -285,6 +314,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([] &i.ManagerName, &i.ManagerPhoneNumber, &i.Balance, + &i.WalletIsActive, ); err != nil { return nil, err } @@ -297,7 +327,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([] } const GetBranchByID = `-- name: GetBranchByID :one -SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance +SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE id = $1 ` @@ -319,12 +349,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er &i.ManagerName, &i.ManagerPhoneNumber, &i.Balance, + &i.WalletIsActive, ) return i, err } const GetBranchByManagerID = `-- name: GetBranchByManagerID :many -SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance +SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE branch_manager_id = $1 ` @@ -352,6 +383,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6 &i.ManagerName, &i.ManagerPhoneNumber, &i.Balance, + &i.WalletIsActive, ); err != nil { return nil, err } @@ -411,7 +443,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge } const SearchBranchByName = `-- name: SearchBranchByName :many -SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance +SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE name ILIKE '%' || $1 || '%' ` @@ -439,6 +471,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) &i.ManagerName, &i.ManagerPhoneNumber, &i.Balance, + &i.WalletIsActive, ); err != nil { return nil, err } diff --git a/gen/db/models.go b/gen/db/models.go index ab7ecca..c10e203 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -143,6 +143,7 @@ type BranchDetail struct { ManagerName interface{} `json:"manager_name"` ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"` Balance pgtype.Int8 `json:"balance"` + WalletIsActive pgtype.Bool `json:"wallet_is_active"` } type BranchOperation struct { @@ -181,6 +182,23 @@ type CustomerWallet struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } +type CustomerWalletDetail struct { + ID int64 `json:"id"` + CustomerID int64 `json:"customer_id"` + RegularID int64 `json:"regular_id"` + RegularBalance int64 `json:"regular_balance"` + StaticID int64 `json:"static_id"` + StaticBalance int64 `json:"static_balance"` + RegularIsActive bool `json:"regular_is_active"` + StaticIsActive bool `json:"static_is_active"` + RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"` + StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + PhoneNumber pgtype.Text `json:"phone_number"` +} + type Event struct { ID string `json:"id"` SportID pgtype.Int4 `json:"sport_id"` diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index c0c3d3c..172469c 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -143,6 +143,46 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet return items, nil } +const GetAllCustomerWallet = `-- name: GetAllCustomerWallet :many +SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number +FROM customer_wallet_details +` + +func (q *Queries) GetAllCustomerWallet(ctx context.Context) ([]CustomerWalletDetail, error) { + rows, err := q.db.Query(ctx, GetAllCustomerWallet) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CustomerWalletDetail + for rows.Next() { + var i CustomerWalletDetail + if err := rows.Scan( + &i.ID, + &i.CustomerID, + &i.RegularID, + &i.RegularBalance, + &i.StaticID, + &i.StaticBalance, + &i.RegularIsActive, + &i.StaticIsActive, + &i.RegularUpdatedAt, + &i.StaticUpdatedAt, + &i.CreatedAt, + &i.FirstName, + &i.LastName, + &i.PhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAllWallets = `-- name: GetAllWallets :many SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance FROM wallets @@ -182,36 +222,14 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { } const GetCustomerWallet = `-- name: GetCustomerWallet :one -SELECT cw.id, - cw.customer_id, - rw.id AS regular_id, - rw.balance AS regular_balance, - sw.id AS static_id, - sw.balance AS static_balance, - rw.updated_at as regular_updated_at, - sw.updated_at as static_updated_at, - cw.created_at -FROM customer_wallets cw - JOIN wallets rw ON cw.regular_wallet_id = rw.id - JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1 +SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number +FROM customer_wallet_details +WHERE customer_id = $1 ` -type GetCustomerWalletRow struct { - ID int64 `json:"id"` - CustomerID int64 `json:"customer_id"` - RegularID int64 `json:"regular_id"` - RegularBalance int64 `json:"regular_balance"` - StaticID int64 `json:"static_id"` - StaticBalance int64 `json:"static_balance"` - RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"` - StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"` - CreatedAt pgtype.Timestamp `json:"created_at"` -} - -func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetCustomerWalletRow, error) { +func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (CustomerWalletDetail, error) { row := q.db.QueryRow(ctx, GetCustomerWallet, customerID) - var i GetCustomerWalletRow + var i CustomerWalletDetail err := row.Scan( &i.ID, &i.CustomerID, @@ -219,9 +237,14 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC &i.RegularBalance, &i.StaticID, &i.StaticBalance, + &i.RegularIsActive, + &i.StaticIsActive, &i.RegularUpdatedAt, &i.StaticUpdatedAt, &i.CreatedAt, + &i.FirstName, + &i.LastName, + &i.PhoneNumber, ) return i, err } diff --git a/internal/domain/bet.go b/internal/domain/bet.go index ce740ce..5571fcb 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -57,10 +57,13 @@ type Bet struct { } type BetFilter struct { - BranchID ValidInt64 // Can Be Nullable - CompanyID ValidInt64 // Can Be Nullable - UserID ValidInt64 // Can Be Nullable - IsShopBet ValidBool + BranchID ValidInt64 // Can Be Nullable + CompanyID ValidInt64 // Can Be Nullable + UserID ValidInt64 // Can Be Nullable + IsShopBet ValidBool + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime } type GetBet struct { diff --git a/internal/domain/branch.go b/internal/domain/branch.go index e6e26fa..6f1be95 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -7,13 +7,17 @@ type Branch struct { WalletID int64 BranchManagerID int64 CompanyID int64 - IsSuspended bool + IsActive bool IsSelfOwned bool } type BranchFilter struct { - CompanyID ValidInt64 - IsSuspended ValidBool + CompanyID ValidInt64 + IsActive ValidBool + BranchManagerID ValidInt64 + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime } type BranchDetail struct { @@ -24,10 +28,11 @@ type BranchDetail struct { Balance Currency BranchManagerID int64 CompanyID int64 - IsSuspended bool + IsActive bool IsSelfOwned bool ManagerName string ManagerPhoneNumber string + WalletIsActive bool } type SupportedOperation struct { @@ -58,7 +63,7 @@ type UpdateBranch struct { BranchManagerID *int64 CompanyID *int64 IsSelfOwned *bool - IsActive *bool + IsActive *bool } type CreateSupportedOperation struct { diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index 993e9b6..7fe8f73 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -15,6 +15,13 @@ type Wallet struct { CreatedAt time.Time } +type WalletFilter struct { + IsActive ValidBool + Query ValidString + CreatedBefore ValidTime + CreatedAfter ValidTime +} + type CustomerWallet struct { ID int64 RegularID int64 @@ -28,9 +35,14 @@ type GetCustomerWallet struct { StaticID int64 StaticBalance Currency CustomerID int64 + RegularIsActive bool + StaticIsActive bool RegularUpdatedAt time.Time StaticUpdatedAt time.Time CreatedAt time.Time + FirstName string + LastName string + PhoneNumber string } type BranchWallet struct { diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 362246c..448b764 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -213,6 +213,18 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma Bool: filter.IsShopBet.Value, Valid: filter.IsShopBet.Valid, }, + Query: pgtype.Text{ + String: filter.Query.Value, + Valid: filter.Query.Valid, + }, + CreatedBefore: pgtype.Timestamp{ + Time: filter.CreatedBefore.Value, + Valid: filter.CreatedBefore.Valid, + }, + CreatedAfter: pgtype.Timestamp{ + Time: filter.CreatedAfter.Value, + Valid: filter.CreatedAfter.Valid, + }, }) if err != nil { domain.MongoDBLogger.Error("failed to get all bets", @@ -312,8 +324,19 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom return err } -func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) { - outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID) +func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) { + + outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{ + EventID: eventID, + FilterStatus: pgtype.Int4{ + Int32: int32(domain.OUTCOME_STATUS_PENDING), + Valid: is_filtered, + }, + FilterStatus2: pgtype.Int4{ + Int32: int32(domain.OUTCOME_STATUS_ERROR), + Valid: is_filtered, + }, + }) if err != nil { domain.MongoDBLogger.Error("failed to get bet outcomes by event ID", zap.Int64("event_id", eventID), @@ -364,6 +387,24 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom return res, nil } +func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) { + update, err := s.queries.UpdateBetOutcomeStatusByBetID(ctx, dbgen.UpdateBetOutcomeStatusByBetIDParams{ + Status: int32(status), + BetID: id, + }) + if err != nil { + domain.MongoDBLogger.Error("failed to update bet outcome status", + zap.Int64("id", id), + zap.Int32("status", int32(status)), + zap.Error(err), + ) + return domain.BetOutcome{}, err + } + + res := convertDBBetOutcomes(update) + return res, nil +} + func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) { outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{ EventID: eventID, @@ -386,10 +427,6 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6 return result, nil } -func (s *Store) DeleteBet(ctx context.Context, id int64) error { - return s.queries.DeleteBet(ctx, id) -} - // GetBetSummary returns aggregated bet statistics func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) ( totalStakes domain.Currency, diff --git a/internal/repository/branch.go b/internal/repository/branch.go index a02370c..e9491bb 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -32,6 +32,8 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail { ManagerName: dbBranch.ManagerName.(string), ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String, Balance: domain.Currency(dbBranch.Balance.Int64), + IsActive: dbBranch.IsActive, + WalletIsActive: dbBranch.WalletIsActive.Bool, } } @@ -140,6 +142,22 @@ func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter) Int64: filter.CompanyID.Value, Valid: filter.CompanyID.Valid, }, + BranchManagerID: pgtype.Int8{ + Int64: filter.BranchManagerID.Value, + Valid: filter.BranchManagerID.Valid, + }, + Query: pgtype.Text{ + String: filter.Query.Value, + Valid: filter.Query.Valid, + }, + CreatedBefore: pgtype.Timestamp{ + Time: filter.CreatedBefore.Value, + Valid: filter.CreatedBefore.Valid, + }, + CreatedAfter: pgtype.Timestamp{ + Time: filter.CreatedAfter.Value, + Valid: filter.CreatedAfter.Valid, + }, }) if err != nil { return nil, err diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 3271b54..f352049 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -47,7 +47,7 @@ func convertCreateCustomerWallet(customerWallet domain.CreateCustomerWallet) dbg } } -func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domain.GetCustomerWallet { +func convertDBGetCustomerWallet(customerWallet dbgen.CustomerWalletDetail) domain.GetCustomerWallet { return domain.GetCustomerWallet{ ID: customerWallet.ID, RegularID: customerWallet.RegularID, @@ -55,9 +55,14 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai StaticID: customerWallet.StaticID, StaticBalance: domain.Currency(customerWallet.StaticBalance), CustomerID: customerWallet.CustomerID, + RegularIsActive: customerWallet.RegularIsActive, + StaticIsActive: customerWallet.StaticIsActive, RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time, StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time, CreatedAt: customerWallet.CreatedAt.Time, + FirstName: customerWallet.FirstName, + LastName: customerWallet.LastName, + PhoneNumber: customerWallet.PhoneNumber.String, } } @@ -115,6 +120,19 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa return result, nil } +func (s *Store) GetAllCustomerWallets(ctx context.Context) ([]domain.GetCustomerWallet, error) { + customerWallets, err := s.queries.GetAllCustomerWallet(ctx) + if err != nil { + return nil, err + } + + var result []domain.GetCustomerWallet = make([]domain.GetCustomerWallet, 0, len(customerWallets)) + for _, wallet := range customerWallets { + result = append(result, convertDBGetCustomerWallet(wallet)) + } + return result, nil +} + func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) { customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID) diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 805b20b..8756667 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -15,14 +15,14 @@ type BetStore interface { GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) - GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) + GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) + UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) - DeleteBet(ctx context.Context, id int64) error GetBetSummary(ctx context.Context, filter domain.ReportFilter) ( totalStakes domain.Currency, diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 932ae2c..fb52bc0 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -30,10 +30,11 @@ var ( ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending") ErrEventHasBeenRemoved = errors.New("Event has been removed") - ErrEventHasNotEnded = errors.New("Event has not ended yet") - ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid") - ErrBranchIDRequired = errors.New("Branch ID required for this role") - ErrOutcomeLimit = errors.New("Too many outcomes on a single bet") + ErrEventHasNotEnded = errors.New("Event has not ended yet") + ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid") + ErrBranchIDRequired = errors.New("Branch ID required for this role") + ErrOutcomeLimit = errors.New("Too many outcomes on a single bet") + ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet") ) type Service struct { @@ -326,7 +327,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID newBet.IsShopBet = true case domain.RoleCustomer: - wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) + wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID) if err != nil { s.mongoLogger.Error("failed to get customer wallets", zap.Int64("user_id", userID), @@ -334,17 +335,52 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID ) return domain.CreateBetRes{}, err } + if req.Amount < wallets.RegularBalance.Float32() { + _, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID, + domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) + if err != nil { + s.mongoLogger.Error("wallet deduction failed for customer regular wallet", + zap.Int64("customer_id", wallets.CustomerID), + zap.Int64("customer_wallet_id", wallets.ID), + zap.Int64("regular wallet_id", wallets.RegularID), + zap.Float32("amount", req.Amount), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } + } else { + combinedBalance := wallets.RegularBalance + wallets.StaticBalance + if req.Amount > combinedBalance.Float32() { + return domain.CreateBetRes{}, ErrTotalBalanceNotEnough + } + // Empty the regular balance + _, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID, + wallets.RegularBalance, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) + if err != nil { + s.mongoLogger.Error("wallet deduction failed for customer regular wallet", + zap.Int64("customer_id", wallets.CustomerID), + zap.Int64("customer_wallet_id", wallets.ID), + zap.Int64("regular wallet_id", wallets.RegularID), + zap.Float32("amount", req.Amount), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } + // Empty remaining from static balance + remainingAmount := wallets.RegularBalance - domain.Currency(req.Amount) + _, err = s.walletSvc.DeductFromWallet(ctx, wallets.StaticID, + remainingAmount, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) + if err != nil { + s.mongoLogger.Error("wallet deduction failed for customer static wallet", + zap.Int64("customer_id", wallets.CustomerID), + zap.Int64("customer_wallet_id", wallets.ID), + zap.Int64("static wallet_id", wallets.StaticID), + zap.Float32("amount", req.Amount), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } - userWallet := wallets[0] - _, err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, - domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) - if err != nil { - s.mongoLogger.Error("wallet deduction failed for customer", - zap.Int64("wallet_id", userWallet.ID), - zap.Float32("amount", req.Amount), - zap.Error(err), - ) - return domain.CreateBetRes{}, err } newBet.UserID = domain.ValidInt64{Value: userID, Valid: true} @@ -838,8 +874,23 @@ func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID in return outcomes, nil } -func (s *Service) DeleteBet(ctx context.Context, id int64) error { - return s.betStore.DeleteBet(ctx, id) +func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error { + _, err := s.betStore.UpdateBetOutcomeStatusByBetID(ctx, id, domain.OUTCOME_STATUS_VOID) + if err != nil { + s.mongoLogger.Error("failed to update bet outcome to void", zap.Int64("id", id), + zap.Error(err), + ) + return err + } + + err = s.betStore.UpdateStatus(ctx, id, domain.OUTCOME_STATUS_VOID) + if err != nil { + s.mongoLogger.Error("failed to update bet to void", zap.Int64("id", id), + zap.Error(err), + ) + return err + } + return nil } func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) { diff --git a/internal/services/result/service.go b/internal/services/result/service.go index 234e548..3ceef62 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -52,7 +52,7 @@ var ( func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, resultRes json.RawMessage, sportID int64) error { // TODO: Optimize this since there could be many outcomes with the same event_id and market_id that could be updated the same time - outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID) + outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, true) if err != nil { s.logger.Error("Failed to get pending bet outcomes", "error", err) return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err) @@ -108,7 +108,7 @@ func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, re } func (s *Service) RefundAllOutcomes(ctx context.Context, eventID int64) error { - outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID) + outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, false) if err != nil { s.logger.Error("Failed to get pending bet outcomes", "error", err) diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index 145f38f..a54433a 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -12,6 +12,7 @@ type WalletStore interface { GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) + GetAllCustomerWallets(ctx context.Context) ([]domain.GetCustomerWallet, error) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index 27de5e4..d7948d9 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -57,6 +57,9 @@ func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wall return s.walletStore.GetWalletsByUser(ctx, id) } +func (s *Service) GetAllCustomerWallet(ctx context.Context) ([]domain.GetCustomerWallet, error) { + return s.walletStore.GetAllCustomerWallets(ctx) +} func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) { return s.walletStore.GetCustomerWallet(ctx, customerID) } @@ -70,7 +73,7 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu } func (s *Service) AddToWallet( - ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, paymentDetails domain. PaymentDetails) (domain.Transfer, error) { + ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, paymentDetails domain.PaymentDetails) (domain.Transfer, error) { wallet, err := s.GetWalletByID(ctx, id) if err != nil { return domain.Transfer{}, err diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 335d07f..30bdde0 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -209,13 +209,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /bet [get] func (h *Handler) GetAllBet(c *fiber.Ctx) error { + role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) branchID := c.Locals("branch_id").(domain.ValidInt64) var isShopBet domain.ValidBool isShopBetQuery := c.Query("is_shop") - - if isShopBetQuery != "" { + if isShopBetQuery != "" && role == domain.RoleSuperAdmin { isShopBetParse, err := strconv.ParseBool(isShopBetQuery) if err != nil { h.mongoLoggerSvc.Error("Failed to parse is_shop_bet", @@ -231,10 +231,47 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error { } } + searchQuery := c.Query("query") + searchString := domain.ValidString{ + Value: searchQuery, + Valid: searchQuery != "", + } + + createdBeforeQuery := c.Query("created_before") + var createdBefore domain.ValidTime + if createdBeforeQuery != "" { + createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdBefore = domain.ValidTime{ + Value: createdBeforeParsed, + Valid: true, + } + } + + createdAfterQuery := c.Query("created_after") + var createdAfter domain.ValidTime + if createdAfterQuery != "" { + createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdAfter = domain.ValidTime{ + Value: createdAfterParsed, + Valid: true, + } + } + bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ - BranchID: branchID, - CompanyID: companyID, - IsShopBet: isShopBet, + BranchID: branchID, + CompanyID: companyID, + IsShopBet: isShopBet, + Query: searchString, + CreatedBefore: createdBefore, + CreatedAfter: createdAfter, }) if err != nil { h.mongoLoggerSvc.Error("Failed to get bets", @@ -432,7 +469,7 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } - err = h.betSvc.DeleteBet(c.Context(), id) + err = h.betSvc.SetBetToRemoved(c.Context(), id) if err != nil { h.mongoLoggerSvc.Error("Failed to delete bet by ID", zap.Int64("betID", id), diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 59fe9bf..d00297e 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -3,6 +3,7 @@ package handlers import ( "strconv" "strings" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" @@ -56,6 +57,7 @@ type BranchRes struct { BranchManagerID int64 `json:"branch_manager_id" example:"1"` CompanyID int64 `json:"company_id" example:"1"` IsSelfOwned bool `json:"is_self_owned" example:"false"` + IsActive bool `json:"is_active" example:"false"` } type BranchDetailRes struct { @@ -69,6 +71,8 @@ type BranchDetailRes struct { ManagerName string `json:"manager_name" example:"John Smith"` ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"` Balance float32 `json:"balance" example:"100.5"` + IsActive bool `json:"is_active" example:"false"` + WalletIsActive bool `json:"is_wallet_active" example:"false"` } func convertBranch(branch domain.Branch) BranchRes { @@ -80,6 +84,7 @@ func convertBranch(branch domain.Branch) BranchRes { BranchManagerID: branch.BranchManagerID, CompanyID: branch.CompanyID, IsSelfOwned: branch.IsSelfOwned, + IsActive: branch.IsActive, } } @@ -95,6 +100,8 @@ func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes { ManagerName: branch.ManagerName, ManagerPhoneNumber: branch.ManagerPhoneNumber, Balance: branch.Balance.Float32(), + IsActive: branch.IsActive, + WalletIsActive: branch.WalletIsActive, } } @@ -391,13 +398,63 @@ func (h *Handler) GetAllBranches(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid is_active param", err, nil) } + branchManagerQuery := c.Query("branch_manager_id") + var branchManagerID domain.ValidInt64 + if branchManagerQuery != "" { + parseManagerID, err := strconv.ParseInt(branchManagerQuery, 10, 64) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Failed to parse branch_manager_id") + } + branchManagerID = domain.ValidInt64{ + Value: parseManagerID, + Valid: true, + } + } + searchQuery := c.Query("query") + searchString := domain.ValidString{ + Value: searchQuery, + Valid: searchQuery != "", + } + + createdBeforeQuery := c.Query("created_before") + var createdBefore domain.ValidTime + if createdBeforeQuery != "" { + createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdBefore = domain.ValidTime{ + Value: createdBeforeParsed, + Valid: true, + } + } + + createdAfterQuery := c.Query("created_after") + var createdAfter domain.ValidTime + if createdAfterQuery != "" { + createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) + if err != nil { + h.logger.Error("invalid start_time format", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) + } + createdAfter = domain.ValidTime{ + Value: createdAfterParsed, + Valid: true, + } + } + branches, err := h.branchSvc.GetAllBranches(c.Context(), domain.BranchFilter{ CompanyID: companyID, - IsSuspended: domain.ValidBool{ + IsActive: domain.ValidBool{ Value: isActive, Valid: isActiveValid, }, + BranchManagerID: branchManagerID, + Query: searchString, + CreatedBefore: createdBefore, + CreatedAfter: createdAfter, }) if err != nil { h.logger.Error("Failed to get branches", "error", err) diff --git a/internal/web_server/handlers/company_handler.go b/internal/web_server/handlers/company_handler.go index 2555cdd..e4d01c2 100644 --- a/internal/web_server/handlers/company_handler.go +++ b/internal/web_server/handlers/company_handler.go @@ -184,7 +184,34 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error { res := convertGetCompany(company) return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) +} +// GetCompanyForAdmin godoc +// @Summary Gets company by id +// @Description Gets a single company by id +// @Tags company +// @Accept json +// @Produce json +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /admin-company [get] +func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + + if !companyID.Valid { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid company ID", nil, nil) + } + company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value) + + if err != nil { + h.logger.Error("Failed to get company by ID", "companyID", companyID.Value, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) + } + + res := convertGetCompany(company) + + return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) } // GetAllCompanies godoc diff --git a/internal/web_server/handlers/mongoLogger.go b/internal/web_server/handlers/mongoLogger.go index 2d97756..1a139f8 100644 --- a/internal/web_server/handlers/mongoLogger.go +++ b/internal/web_server/handlers/mongoLogger.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "os" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/gofiber/fiber/v2" @@ -20,7 +21,7 @@ import ( // @Router /api/v1/logs [get] func GetLogsHandler(appCtx context.Context) fiber.Handler { return func(c *fiber.Ctx) error { - client, err := mongo.Connect(appCtx, options.Client().ApplyURI("mongodb://root:secret@mongo:27017/?authSource=admin")) + client, err := mongo.Connect(appCtx, options.Client().ApplyURI(os.Getenv("MONGODB_URL"))) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error()) } diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 243ac76..a088698 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -174,11 +174,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error") } - newWallet, err := h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ - UserID: newUser.ID, - IsWithdraw: true, - IsBettable: true, - }) + newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID) if err != nil { h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet") @@ -193,7 +189,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { // TODO: Remove later _, err = h.walletSvc.AddToWallet( - c.Context(), newWallet.ID, domain.ToCurrency(100.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + c.Context(), newWallet.RegularID, domain.ToCurrency(100.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) if err != nil { h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err) diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index 443e7ce..853a4af 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -9,9 +9,6 @@ import ( "github.com/gofiber/fiber/v2" ) -type UpdateWalletActiveReq struct { - IsActive bool `json:"is_active" validate:"required" example:"true"` -} type WalletRes struct { ID int64 `json:"id" example:"1"` Balance float32 `json:"amount" example:"100.0"` @@ -45,9 +42,14 @@ type CustomerWalletRes struct { StaticID int64 `json:"static_id" example:"1"` StaticBalance float32 `json:"static_balance" example:"100.0"` CustomerID int64 `json:"customer_id" example:"1"` + RegularIsActive bool `json:"regular_is_active" example:"true"` + StaticIsActive bool `json:"static_is_active" example:"true"` RegularUpdatedAt time.Time `json:"regular_updated_at"` StaticUpdatedAt time.Time `json:"static_updated_at"` CreatedAt time.Time `json:"created_at"` + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` } func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes { @@ -58,9 +60,14 @@ func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes { StaticID: wallet.StaticID, StaticBalance: wallet.StaticBalance.Float32(), CustomerID: wallet.CustomerID, + RegularIsActive: wallet.RegularIsActive, + StaticIsActive: wallet.StaticIsActive, RegularUpdatedAt: wallet.RegularUpdatedAt, StaticUpdatedAt: wallet.StaticUpdatedAt, CreatedAt: wallet.CreatedAt, + FirstName: wallet.FirstName, + LastName: wallet.LastName, + PhoneNumber: wallet.PhoneNumber, } } @@ -173,7 +180,38 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error { } return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) +} +// GetAllCustomerWallets godoc +// @Summary Get all customer wallets +// @Description Retrieve all customer wallets +// @Tags wallet +// @Accept json +// @Produce json +// @Success 200 {array} CustomerWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /customerWallet [get] +func (h *Handler) GetAllCustomerWallets(c *fiber.Ctx) error { + + wallets, err := h.walletSvc.GetAllCustomerWallet(c.Context()) + + if err != nil { + h.logger.Error("Failed to get wallets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) + } + + var res []CustomerWalletRes = make([]CustomerWalletRes, 0, len(wallets)) + + for _, wallet := range wallets { + res = append(res, ConvertCustomerWallet(wallet)) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) +} + +type UpdateWalletActiveReq struct { + IsActive bool `json:"is_active" validate:"required" example:"true"` } // UpdateWalletActive godoc @@ -255,13 +293,13 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error { // h.logger.Info("Fetching customer wallet", "userID", userID) - wallet, err := h.walletSvc.GetWalletsByUser(c.Context(), userID) + wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID) if err != nil { h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet") } - res := convertWallet(wallet[0]) + res := ConvertCustomerWallet(wallet) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 91d176a..ee1c8dc 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -174,6 +174,7 @@ func (a *App) initAppRoutes() { a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany) a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID) a.fiber.Get("/search/company", a.authMiddleware, h.SearchCompany) + a.fiber.Get("/admin-company", a.authMiddleware, h.GetCompanyForAdmin) // Ticket Routes a.fiber.Post("/ticket", h.CreateTicket) @@ -195,6 +196,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/wallet/:id", h.GetWalletByID) a.fiber.Put("/wallet/:id", h.UpdateWalletActive) a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets) + a.fiber.Get("/customerWallet", a.authMiddleware, h.GetAllCustomerWallets) a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier) // Transfer