From 1557a3141bdbe57a47a1e0b71011d04e22a67867 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 16 Jun 2025 16:24:42 +0300 Subject: [PATCH] fix: transfer not showing online bet issue --- .env | 8 +- db.sql | 17 ++ db/migrations/000001_fortune.up.sql | 26 ++- db/migrations/000006_recommendation.up.sql | 36 ++-- db/query/bet.sql | 16 +- db/query/branch.sql | 10 +- gen/db/bet.sql.go | 12 +- gen/db/branch.sql.go | 17 +- gen/db/models.go | 9 +- gen/db/transfer.sql.go | 4 +- internal/domain/bet.go | 2 +- internal/domain/branch.go | 5 + internal/domain/notification.go | 47 ++-- internal/domain/transfer.go | 20 +- internal/domain/wallet.go | 8 + internal/logger/mongoLogger/init.go | 2 +- internal/repository/bet.go | 4 + internal/repository/branch.go | 9 +- internal/repository/transfer.go | 36 ++-- internal/services/bet/service.go | 17 +- internal/services/branch/port.go | 2 +- internal/services/branch/service.go | 6 +- internal/services/chapa/service.go | 21 +- internal/services/referal/service.go | 6 +- internal/services/virtualGame/Alea/service.go | 2 +- internal/services/virtualGame/service.go | 8 +- internal/services/virtualGame/veli/service.go | 2 +- internal/services/wallet/transfer.go | 204 +++++++----------- internal/services/wallet/wallet.go | 139 +++++++++++- internal/web_server/cron.go | 44 ++-- internal/web_server/handlers/auth_handler.go | 5 +- internal/web_server/handlers/bet_handler.go | 19 +- .../web_server/handlers/branch_handler.go | 19 +- internal/web_server/handlers/manager.go | 47 ++-- internal/web_server/handlers/mongoLogger.go | 3 +- .../web_server/handlers/transfer_handler.go | 44 ++-- internal/web_server/handlers/user.go | 4 +- internal/web_server/middleware.go | 16 ++ internal/web_server/routes.go | 2 +- logs/app.log | 2 + 40 files changed, 585 insertions(+), 315 deletions(-) create mode 100644 db.sql diff --git a/.env b/.env index ea411f2..490c4a9 100644 --- a/.env +++ b/.env @@ -1,8 +1,8 @@ # REPORT_EXPORT_PATH="C:\\ProgramData\\FortuneBet\\exported_reports" #prod env REPORT_EXPORT_PATH ="./exported_reports" #dev env -RESEND_SENDER_EMAIL=email -RESEND_API_KEY=123 +RESEND_SENDER_EMAIL=customer@fortunebets.net +RESEND_API_KEY=re_GSTRa9Pp_JkRWBpST9MvaCVULJF8ybGKE ENV=development PORT=8080 @@ -11,8 +11,8 @@ REFRESH_EXPIRY=2592000 JWT_KEY=mysecretkey ACCESS_EXPIRY=600 LOG_LEVEL=debug -AFRO_SMS_API_KEY=1 -AFRO_SMS_SENDER_NAME= +AFRO_SMS_API_KEY=eyJhbGciOiJIUzI1NiJ9.eyJpZGVudGlmaWVyIjoiQlR5ZDFIYmJFYXZ6YUo3dzZGell1RUlieGozSElJeTYiLCJleHAiOjE4OTYwMTM5MTksImlhdCI6MTczODI0NzUxOSwianRpIjoiOWIyNTJkNWQtODcxOC00NGYzLWIzMDQtMGYxOTRhY2NiNTU3In0.XPw8s6mCx1Tp1CfxGmjFRROmdkVnghnqfmsniB-Ze8I +AFRO_SMS_SENDER_NAME=FortuneBets AFRO_SMS_RECEIVER_PHONE_NUMBER= BET365_TOKEN=158046-hesJDP2Cay2M5G diff --git a/db.sql b/db.sql new file mode 100644 index 0000000..c149b92 --- /dev/null +++ b/db.sql @@ -0,0 +1,17 @@ +psql (16.8) +Type "help" for help. + +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=# +gh=#  +gh=# \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index c43a7b9..14add7f 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -125,7 +125,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, type VARCHAR(255) NOT NULL, - receiver_wallet_id BIGINT NOT NULL, + receiver_wallet_id BIGINT, sender_wallet_id BIGINT, cashier_id BIGINT, verified BOOLEAN NOT NULL DEFAULT false, @@ -263,7 +263,6 @@ FROM companies JOIN wallets ON wallets.id = companies.wallet_id JOIN users ON users.id = companies.admin_id; ; - CREATE VIEW branch_details AS SELECT branches.*, CONCAT(users.first_name, ' ', users.last_name) AS manager_name, @@ -290,40 +289,39 @@ FROM tickets LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id GROUP BY tickets.id; -- Foreign Keys - ALTER TABLE users - ADD CONSTRAINT unique_email UNIQUE (email), +ADD CONSTRAINT unique_email UNIQUE (email), ADD CONSTRAINT unique_phone_number UNIQUE (phone_number); ALTER TABLE refresh_tokens - ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE bets - ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), +ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); ALTER TABLE wallets - ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE customer_wallets - ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), +ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id); ALTER TABLE wallet_transfer - ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), +ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); ALTER TABLE transactions - ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), +ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id); ALTER TABLE branches - ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), +ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id); ALTER TABLE branch_operations - ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, +ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ALTER TABLE branch_cashiers - ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, +ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ALTER TABLE companies - ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), +ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE; ----------------------------------------------seed data------------------------------------------------------------- -------------------------------------- DO NOT USE IN PRODUCTION------------------------------------------------- diff --git a/db/migrations/000006_recommendation.up.sql b/db/migrations/000006_recommendation.up.sql index 6be9fc7..0e83986 100644 --- a/db/migrations/000006_recommendation.up.sql +++ b/db/migrations/000006_recommendation.up.sql @@ -1,28 +1,28 @@ -CREATE TABLE virtual_games ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - provider VARCHAR(100) NOT NULL, - category VARCHAR(100) NOT NULL, - min_bet DECIMAL(15,2) NOT NULL, - max_bet DECIMAL(15,2) NOT NULL, - volatility VARCHAR(50) NOT NULL, - rtp DECIMAL(5,2) NOT NULL, - is_featured BOOLEAN DEFAULT false, - popularity_score INTEGER DEFAULT 0, - thumbnail_url TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); +-- CREATE TABLE virtual_games ( +-- id BIGSERIAL PRIMARY KEY, +-- name VARCHAR(255) NOT NULL, +-- provider VARCHAR(100) NOT NULL, +-- category VARCHAR(100) NOT NULL, +-- min_bet DECIMAL(15,2) NOT NULL, +-- max_bet DECIMAL(15,2) NOT NULL, +-- volatility VARCHAR(50) NOT NULL, +-- rtp DECIMAL(5,2) NOT NULL, +-- is_featured BOOLEAN DEFAULT false, +-- popularity_score INTEGER DEFAULT 0, +-- thumbnail_url TEXT, +-- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, +-- updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +-- ); CREATE TABLE user_game_interactions ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id), game_id BIGINT NOT NULL REFERENCES virtual_games(id), - interaction_type VARCHAR(50) NOT NULL, -- 'view', 'play', 'bet', 'favorite' - amount DECIMAL(15,2), + interaction_type VARCHAR(50) NOT NULL, + -- 'view', 'play', 'bet', 'favorite' + amount DECIMAL(15, 2), duration_seconds INTEGER, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); - CREATE INDEX idx_user_game_interactions_user ON user_game_interactions(user_id); CREATE INDEX idx_user_game_interactions_game ON user_game_interactions(game_id); \ No newline at end of file diff --git a/db/query/bet.sql b/db/query/bet.sql index 335cf56..8686f6b 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -48,16 +48,20 @@ VALUES ( SELECT * FROM bet_with_outcomes wHERE ( - branch_id = $1 - OR $1 IS NULL + branch_id = sqlc.narg('branch_id') + OR sqlc.narg('branch_id') IS NULL ) AND ( - company_id = $2 - OR $2 IS NULL + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL ) AND ( - user_id = $3 - OR $3 IS NULL + user_id = sqlc.narg('user_id') + OR sqlc.narg('user_id') IS NULL + ) + AND ( + is_shop_bet = sqlc.narg('is_shop_bet') + OR sqlc.narg('is_shop_bet') IS NULL ); -- name: GetBetByID :one SELECT * diff --git a/db/query/branch.sql b/db/query/branch.sql index bb01b26..eef1ae1 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -23,7 +23,15 @@ VALUES ($1, $2) RETURNING *; -- name: GetAllBranches :many SELECT * -FROM branch_details; +FROM branch_details +WHERE ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL + ); -- name: GetBranchByID :one SELECT * FROM branch_details diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 40182ae..1852a08 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -125,16 +125,26 @@ wHERE ( user_id = $3 OR $3 IS NULL ) + AND ( + is_shop_bet = $4 + OR $4 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"` } func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) { - rows, err := q.db.Query(ctx, GetAllBets, arg.BranchID, arg.CompanyID, arg.UserID) + rows, err := q.db.Query(ctx, GetAllBets, + arg.BranchID, + arg.CompanyID, + arg.UserID, + arg.IsShopBet, + ) if err != nil { return nil, err } diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index d3ef2e5..d762fa8 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -157,10 +157,23 @@ 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 FROM branch_details +WHERE ( + company_id = $1 + OR $1 IS NULL + ) + AND ( + is_active = $2 + OR $2 IS NULL + ) ` -func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) { - rows, err := q.db.Query(ctx, GetAllBranches) +type GetAllBranchesParams struct { + CompanyID pgtype.Int8 `json:"company_id"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, GetAllBranches, arg.CompanyID, arg.IsActive) if err != nil { return nil, err } diff --git a/gen/db/models.go b/gen/db/models.go index 420586e..de9c74b 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -421,12 +421,13 @@ type VirtualGame struct { ID int64 `json:"id"` Name string `json:"name"` Provider string `json:"provider"` - Category string `json:"category"` + Category pgtype.Text `json:"category"` MinBet pgtype.Numeric `json:"min_bet"` MaxBet pgtype.Numeric `json:"max_bet"` - Volatility string `json:"volatility"` + Volatility pgtype.Text `json:"volatility"` + IsActive bool `json:"is_active"` Rtp pgtype.Numeric `json:"rtp"` - IsFeatured pgtype.Bool `json:"is_featured"` + IsFeatured bool `json:"is_featured"` PopularityScore pgtype.Int4 `json:"popularity_score"` ThumbnailUrl pgtype.Text `json:"thumbnail_url"` CreatedAt pgtype.Timestamptz `json:"created_at"` @@ -483,7 +484,7 @@ type WalletTransfer struct { ID int64 `json:"id"` Amount int64 `json:"amount"` Type string `json:"type"` - ReceiverWalletID int64 `json:"receiver_wallet_id"` + ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"` SenderWalletID pgtype.Int8 `json:"sender_wallet_id"` CashierID pgtype.Int8 `json:"cashier_id"` Verified bool `json:"verified"` diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index 2c8e6f6..3e5c65e 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -29,7 +29,7 @@ RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, ve type CreateTransferParams struct { Amount int64 `json:"amount"` Type string `json:"type"` - ReceiverWalletID int64 `json:"receiver_wallet_id"` + ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"` SenderWalletID pgtype.Int8 `json:"sender_wallet_id"` CashierID pgtype.Int8 `json:"cashier_id"` Verified bool `json:"verified"` @@ -159,7 +159,7 @@ WHERE receiver_wallet_id = $1 OR sender_wallet_id = $1 ` -func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID int64) ([]WalletTransfer, error) { +func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgtype.Int8) ([]WalletTransfer, error) { rows, err := q.db.Query(ctx, GetTransfersByWallet, receiverWalletID) if err != nil { return nil, err diff --git a/internal/domain/bet.go b/internal/domain/bet.go index cbd904e..832686b 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -60,6 +60,7 @@ type BetFilter struct { BranchID ValidInt64 // Can Be Nullable CompanyID ValidInt64 // Can Be Nullable UserID ValidInt64 // Can Be Nullable + IsShopBet ValidBool } type GetBet struct { @@ -173,4 +174,3 @@ func ConvertBet(bet GetBet) BetRes { CreatedAt: bet.CreatedAt, } } - diff --git a/internal/domain/branch.go b/internal/domain/branch.go index 43d2cc0..d27eb08 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -11,6 +11,11 @@ type Branch struct { IsSelfOwned bool } +type BranchFilter struct { + CompanyID ValidInt64 + IsSuspended ValidBool +} + type BranchDetail struct { ID int64 Name string diff --git a/internal/domain/notification.go b/internal/domain/notification.go index bcad707..5905b31 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -14,18 +14,20 @@ type NotificationDeliveryStatus string type DeliveryChannel string const ( - NotificationTypeCashOutSuccess NotificationType = "cash_out_success" - NotificationTypeDepositSuccess NotificationType = "deposit_success" - NotificationTypeBetPlaced NotificationType = "bet_placed" - NotificationTypeDailyReport NotificationType = "daily_report" - NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet" - NotificationTypeBetOverload NotificationType = "bet_overload" - NotificationTypeSignUpWelcome NotificationType = "signup_welcome" - NotificationTypeOTPSent NotificationType = "otp_sent" - NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold" - NOTIFICATION_TYPE_TRANSFER NotificationType = "transfer_failed" - NOTIFICATION_TYPE_ADMIN_ALERT NotificationType = "admin_alert" - NOTIFICATION_RECEIVER_ADMIN NotificationRecieverSide = "admin" + NotificationTypeCashOutSuccess NotificationType = "cash_out_success" + NotificationTypeDepositSuccess NotificationType = "deposit_success" + NotificationTypeWithdrawSuccess NotificationType = "withdraw_success" + NotificationTypeBetPlaced NotificationType = "bet_placed" + NotificationTypeDailyReport NotificationType = "daily_report" + NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet" + NotificationTypeBetOverload NotificationType = "bet_overload" + NotificationTypeSignUpWelcome NotificationType = "signup_welcome" + NotificationTypeOTPSent NotificationType = "otp_sent" + NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold" + NOTIFICATION_TYPE_TRANSFER_FAIL NotificationType = "transfer_failed" + NOTIFICATION_TYPE_TRANSFER_SUCCESS NotificationType = "transfer_success" + NOTIFICATION_TYPE_ADMIN_ALERT NotificationType = "admin_alert" + NOTIFICATION_RECEIVER_ADMIN NotificationRecieverSide = "admin" NotificationRecieverSideAdmin NotificationRecieverSide = "admin" NotificationRecieverSideCustomer NotificationRecieverSide = "customer" @@ -57,9 +59,9 @@ const ( ) type NotificationPayload struct { - Headline string `json:"headline"` - Message string `json:"message"` - Tags []string `json:"tags"` + Headline string `json:"headline"` + Message string `json:"message"` + Tags []string `json:"tags"` } type Notification struct { @@ -91,3 +93,18 @@ func FromJSON(data []byte) (*Notification, error) { } return &n, nil } + +func ReceiverFromRole(role Role) NotificationRecieverSide { + + if role == RoleAdmin { + return NotificationRecieverSideAdmin + } else if role == RoleCashier { + return NotificationRecieverSideCashier + } else if role == RoleBranchManager { + return NotificationRecieverSideBranchManager + } else if role == RoleCustomer { + return NotificationRecieverSideCustomer + } else { + return "" + } +} diff --git a/internal/domain/transfer.go b/internal/domain/transfer.go index bf968d2..ed8411c 100644 --- a/internal/domain/transfer.go +++ b/internal/domain/transfer.go @@ -12,7 +12,10 @@ const ( type PaymentMethod string +// Info on why the wallet was modified +// If its internal system modification then, its always direct const ( + TRANSFER_DIRECT PaymentMethod = "direct" TRANSFER_CASH PaymentMethod = "cash" TRANSFER_BANK PaymentMethod = "bank" TRANSFER_CHAPA PaymentMethod = "chapa" @@ -22,16 +25,21 @@ const ( TRANSFER_OTHER PaymentMethod = "other" ) -// There is always a receiving wallet id -// There is a sender wallet id only if wallet transfer type +// Info for the payment providers +type PaymentDetails struct { + ReferenceNumber ValidString + BankNumber ValidString +} + +// A Transfer is logged for every modification of ALL wallets and wallet types type Transfer struct { ID int64 Amount Currency Verified bool Type TransferType PaymentMethod PaymentMethod - ReceiverWalletID int64 - SenderWalletID int64 + ReceiverWalletID ValidInt64 + SenderWalletID ValidInt64 ReferenceNumber string CashierID ValidInt64 CreatedAt time.Time @@ -42,8 +50,8 @@ type CreateTransfer struct { Amount Currency Verified bool ReferenceNumber string - ReceiverWalletID int64 - SenderWalletID int64 + ReceiverWalletID ValidInt64 + SenderWalletID ValidInt64 CashierID ValidInt64 Type TransferType PaymentMethod PaymentMethod diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index 387dbd7..5620c87 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -57,3 +57,11 @@ type CreateCustomerWallet struct { RegularWalletID int64 StaticWalletID int64 } + +type WalletType string + +const ( + CustomerWalletType WalletType = "customer_wallet" + BranchWalletType WalletType = "branch_wallet" + CompanyWalletType WalletType = "company_wallet" +) diff --git a/internal/logger/mongoLogger/init.go b/internal/logger/mongoLogger/init.go index 9d4b78b..77ef645 100644 --- a/internal/logger/mongoLogger/init.go +++ b/internal/logger/mongoLogger/init.go @@ -10,7 +10,7 @@ import ( func InitLogger() (*zap.Logger, error) { mongoCore, err := NewMongoCore( - "mongodb://root:secret@mongo:27017/?authSource=admin", + "mongodb://root:secret@localhost:27017/?authSource=admin", "logdb", "applogs", zapcore.InfoLevel, diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 560eb62..14dc385 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -209,6 +209,10 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma Int64: filter.UserID.Value, Valid: filter.UserID.Valid, }, + IsShopBet: pgtype.Bool{ + Bool: filter.IsShopBet.Value, + Valid: filter.IsShopBet.Valid, + }, }) if err != nil { domain.MongoDBLogger.Error("failed to get all bets", diff --git a/internal/repository/branch.go b/internal/repository/branch.go index 51f460f..f504a0f 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -128,8 +128,13 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do return branches, nil } -func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { - dbBranches, err := s.queries.GetAllBranches(ctx) +func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.GetAllBranches(ctx, dbgen.GetAllBranchesParams{ + CompanyID: pgtype.Int8{ + Int64: filter.CompanyID.Value, + Valid: filter.CompanyID.Valid, + }, + }) if err != nil { return nil, err } diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go index 58d3b05..8b2d0b7 100644 --- a/internal/repository/transfer.go +++ b/internal/repository/transfer.go @@ -10,12 +10,18 @@ import ( func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer { return domain.Transfer{ - ID: transfer.ID, - Amount: domain.Currency(transfer.Amount), - Type: domain.TransferType(transfer.Type), - Verified: transfer.Verified, - ReceiverWalletID: transfer.ReceiverWalletID, - SenderWalletID: transfer.SenderWalletID.Int64, + ID: transfer.ID, + Amount: domain.Currency(transfer.Amount), + Type: domain.TransferType(transfer.Type), + Verified: transfer.Verified, + ReceiverWalletID: domain.ValidInt64{ + Value: transfer.ReceiverWalletID.Int64, + Valid: transfer.ReceiverWalletID.Valid, + }, + SenderWalletID: domain.ValidInt64{ + Value: transfer.SenderWalletID.Int64, + Valid: transfer.SenderWalletID.Valid, + }, CashierID: domain.ValidInt64{ Value: transfer.CashierID.Int64, Valid: transfer.CashierID.Valid, @@ -26,12 +32,15 @@ func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer { func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams { return dbgen.CreateTransferParams{ - Amount: int64(transfer.Amount), - Type: string(transfer.Type), - ReceiverWalletID: transfer.ReceiverWalletID, + Amount: int64(transfer.Amount), + Type: string(transfer.Type), + ReceiverWalletID: pgtype.Int8{ + Int64: transfer.ReceiverWalletID.Value, + Valid: transfer.ReceiverWalletID.Valid, + }, SenderWalletID: pgtype.Int8{ - Int64: transfer.SenderWalletID, - Valid: true, + Int64: transfer.SenderWalletID.Value, + Valid: transfer.SenderWalletID.Valid, }, CashierID: pgtype.Int8{ Int64: transfer.CashierID.Value, @@ -62,7 +71,10 @@ func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) return result, nil } func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { - transfers, err := s.queries.GetTransfersByWallet(ctx, walletID) + transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{ + Int64: walletID, + Valid: true, + }) if err != nil { return nil, err } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 59d0bc0..ae8de24 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -241,7 +241,11 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } deductedAmount := req.Amount / 10 - err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) + _, err = s.walletSvc.DeductFromWallet(ctx, + branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{ + Value: userID, + Valid: true, + }, domain.TRANSFER_DIRECT) if err != nil { s.mongoLogger.Error("failed to deduct from wallet", zap.Int64("wallet_id", branch.WalletID), @@ -274,7 +278,10 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } deductedAmount := req.Amount / 10 - err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) + _, err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{ + Value: userID, + Valid: true, + }, domain.TRANSFER_DIRECT) if err != nil { s.mongoLogger.Error("wallet deduction failed", zap.Int64("wallet_id", branch.WalletID), @@ -300,7 +307,8 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } userWallet := wallets[0] - err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount)) + _, 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), @@ -676,7 +684,8 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc amount = bet.Amount } - err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount) + _, err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + if err != nil { s.mongoLogger.Error("failed to add winnings to wallet", zap.Int64("wallet_id", customerWallet.RegularID), diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go index a128d59..3f242c8 100644 --- a/internal/services/branch/port.go +++ b/internal/services/branch/port.go @@ -11,7 +11,7 @@ type BranchStore interface { GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) - GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) + GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) DeleteBranch(ctx context.Context, id int64) error diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go index eb75170..eccb764 100644 --- a/internal/services/branch/service.go +++ b/internal/services/branch/service.go @@ -1,4 +1,4 @@ -package branch + package branch import ( "context" @@ -42,8 +42,8 @@ func (s *Service) GetBranchByCompanyID(ctx context.Context, companyID int64) ([] func (s *Service) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) { return s.branchStore.GetBranchOperations(ctx, branchID) } -func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { - return s.branchStore.GetAllBranches(ctx) +func (s *Service) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) { + return s.branchStore.GetAllBranches(ctx, filter) } func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) { diff --git a/internal/services/chapa/service.go b/internal/services/chapa/service.go index cb9281a..158b2e2 100644 --- a/internal/services/chapa/service.go +++ b/internal/services/chapa/service.go @@ -82,8 +82,11 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma PaymentMethod: domain.TRANSFER_CHAPA, ReferenceNumber: reference, // ReceiverWalletID: 1, - SenderWalletID: senderWallet.ID, - Verified: false, + SenderWalletID: domain.ValidInt64{ + Value: senderWallet.ID, + Valid: true, + }, + Verified: false, } if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil { @@ -119,6 +122,11 @@ func (s *Service) VerifyDeposit(ctx context.Context, reference string) error { return ErrPaymentNotFound } + // just making sure that the sender id is valid + if !payment.SenderWalletID.Valid { + return fmt.Errorf("sender wallet is invalid %v \n", payment.SenderWalletID) + } + // Skip if already completed if payment.Verified { return nil @@ -137,7 +145,7 @@ func (s *Service) VerifyDeposit(ctx context.Context, reference string) error { // If payment is completed, credit user's wallet if verification.Status == domain.PaymentStatusCompleted { - if err := s.walletStore.UpdateBalance(ctx, payment.SenderWalletID, payment.Amount); err != nil { + if err := s.walletStore.UpdateBalance(ctx, payment.SenderWalletID.Value, payment.Amount); err != nil { return fmt.Errorf("failed to credit user wallet: %w", err) } } @@ -156,6 +164,11 @@ func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domai }, nil } + // just making sure that the sender id is valid + if !transfer.SenderWalletID.Valid { + return nil, fmt.Errorf("sender wallet id is invalid: %v \n", transfer.SenderWalletID) + } + // If not verified or not found, verify with Chapa verification, err := s.chapaClient.VerifyPayment(ctx, txRef) if err != nil { @@ -170,7 +183,7 @@ func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domai } // Credit user's wallet - err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID, transfer.Amount) + err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID.Value, transfer.Amount) if err != nil { return nil, fmt.Errorf("failed to update wallet balance: %w", err) } diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index 4c1b5b8..bbb0d43 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -124,7 +124,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo walletID := wallets[0].ID currentBonus := float64(wallets[0].Balance) - err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100))) + _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) if err != nil { s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err) return err @@ -162,7 +162,7 @@ func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amo walletID := wallets[0].ID bonus := amount * (settings.CashbackPercentage / 100) currentBonus := float64(wallets[0].Balance) - err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100))) + _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+bonus)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) if err != nil { s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err) return err @@ -216,7 +216,7 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA walletID := wallets[0].ID currentBalance := float64(wallets[0].Balance) - err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100))) + _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBalance+bonus)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) if err != nil { s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err) return err diff --git a/internal/services/virtualGame/Alea/service.go b/internal/services/virtualGame/Alea/service.go index aadd179..f3f9a9f 100644 --- a/internal/services/virtualGame/Alea/service.go +++ b/internal/services/virtualGame/Alea/service.go @@ -128,7 +128,7 @@ func (s *AleaPlayService) processTransaction(ctx context.Context, tx *domain.Vir } tx.WalletID = wallets[0].ID - if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil { + if _, err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil { return fmt.Errorf("wallet update failed: %w", err) } diff --git a/internal/services/virtualGame/service.go b/internal/services/virtualGame/service.go index b1e28d0..c219b8a 100644 --- a/internal/services/virtualGame/service.go +++ b/internal/services/virtualGame/service.go @@ -117,7 +117,7 @@ func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCall return errors.New("unknown transaction type") } - err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(amount)) + _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) if err != nil { s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", err) return err @@ -184,7 +184,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) ( return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets") } - if err := s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil { + if _, err := s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT); err != nil { return nil, fmt.Errorf("insufficient balance") } @@ -245,7 +245,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) ( // 4. Credit to wallet - if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil { + if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil { s.logger.Error("Failed to credit wallet", "userID", claims.UserID, "error", err) return nil, fmt.Errorf("wallet credit failed") } @@ -316,7 +316,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ // 5. Refund the bet amount (absolute value since bet amount is negative) refundAmount := -originalBet.Amount - if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount)); err != nil { + if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil { s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err) return nil, fmt.Errorf("refund failed") } diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index fc9097a..2474eab 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -150,7 +150,7 @@ func (s *VeliPlayService) processTransaction(ctx context.Context, tx *domain.Vir } tx.WalletID = wallets[0].ID - if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil { + if _, err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil { return fmt.Errorf("wallet update failed: %w", err) } diff --git a/internal/services/wallet/transfer.go b/internal/services/wallet/transfer.go index 7f71c4a..ec29ce1 100644 --- a/internal/services/wallet/transfer.go +++ b/internal/services/wallet/transfer.go @@ -14,85 +14,7 @@ var ( ) func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { - senderWallet, err := s.walletStore.GetWalletByID(ctx, transfer.SenderWalletID) - receiverWallet, err := s.walletStore.GetWalletByID(ctx, transfer.ReceiverWalletID) - if err != nil { - return domain.Transfer{}, fmt.Errorf("failed to get sender wallet: %w", err) - } - - // Check if wallet has sufficient balance - if senderWallet.Balance < transfer.Amount || senderWallet.Balance == 0 { - // Send notification to customer - customerNotification := &domain.Notification{ - RecipientID: receiverWallet.UserID, - Type: domain.NOTIFICATION_TYPE_TRANSFER, - Level: domain.NotificationLevelError, - Reciever: domain.NotificationRecieverSideCustomer, - DeliveryChannel: domain.DeliveryChannelInApp, - Payload: domain.NotificationPayload{ - Headline: "Service Temporarily Unavailable", - Message: "Our payment system is currently under maintenance. Please try again later.", - }, - Priority: 2, - Metadata: []byte(fmt.Sprintf(`{ - "transfer_amount": %d, - "current_balance": %d, - "wallet_id": %d, - "notification_type": "customer_facing" - }`, transfer.Amount, senderWallet.Balance, transfer.SenderWalletID)), - } - - // Send notification to admin team - adminNotification := &domain.Notification{ - RecipientID: senderWallet.UserID, - Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT, - Level: domain.NotificationLevelError, - Reciever: domain.NotificationRecieverSideAdmin, - DeliveryChannel: domain.DeliveryChannelEmail, // Or any preferred admin channel - Payload: domain.NotificationPayload{ - Headline: "CREDIT WARNING: System Running Out of Funds", - Message: fmt.Sprintf( - "Wallet ID %d has insufficient balance for transfer. Current balance: %.2f, Attempted transfer: %.2f", - transfer.SenderWalletID, - float64(senderWallet.Balance)/100, - float64(transfer.Amount)/100, - ), - }, - Priority: 1, // High priority for admin alerts - Metadata: fmt.Appendf(nil, `{ - "wallet_id": %d, - "balance": %d, - "required_amount": %d, - "notification_type": "admin_alert" - }`, transfer.SenderWalletID, senderWallet.Balance, transfer.Amount), - } - - // Send both notifications - if err := s.notificationStore.SendNotification(ctx, customerNotification); err != nil { - s.logger.Error("failed to send customer notification", - "user_id", "", - "error", err) - } - - // Get admin recipients and send to all - adminRecipients, err := s.notificationStore.ListRecipientIDs(ctx, domain.NotificationRecieverSideAdmin) - if err != nil { - s.logger.Error("failed to get admin recipients", "error", err) - } else { - for _, adminID := range adminRecipients { - adminNotification.RecipientID = adminID - if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { - s.logger.Error("failed to send admin notification", - "admin_id", adminID, - "error", err) - } - } - } - - return domain.Transfer{}, ErrInsufficientBalance - } - - // Proceed with transfer if balance is sufficient + // This is just a transfer log when return s.transferStore.CreateTransfer(ctx, transfer) } @@ -116,43 +38,12 @@ func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, veri return s.transferStore.UpdateTransferVerification(ctx, id, verified) } -func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { - receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID) - if err != nil { - return domain.Transfer{}, err - } - // Add to receiver - senderWallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID) - if err != nil { - return domain.Transfer{}, err - } else if senderWallet.Balance < transfer.Amount { - return domain.Transfer{}, ErrInsufficientBalance - } - err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount) - if err != nil { - return domain.Transfer{}, err - } - - // Log the transfer so that if there is a mistake, it can be reverted - newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ - CashierID: transfer.CashierID, - ReceiverWalletID: receiverWallet.ID, - Amount: transfer.Amount, - Type: domain.DEPOSIT, - PaymentMethod: transfer.PaymentMethod, - Verified: true, - }) - if err != nil { - return domain.Transfer{}, err - } - - return newTransfer, nil - -} - -func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64) (domain.Transfer, error) { +func (s *Service) TransferToWallet(ctx context.Context, + senderID int64, receiverID int64, + amount domain.Currency, paymentMethod domain.PaymentMethod, + cashierID domain.ValidInt64) (domain.Transfer, error) { senderWallet, err := s.GetWalletByID(ctx, senderID) if err != nil { @@ -191,14 +82,20 @@ func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiver } // Log the transfer so that if there is a mistake, it can be reverted - transfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ - SenderWalletID: senderID, - CashierID: cashierID, - ReceiverWalletID: receiverID, - Amount: amount, - Type: domain.WALLET, - PaymentMethod: paymentMethod, - Verified: true, + transfer, err := s.CreateTransfer(ctx, domain.CreateTransfer{ + SenderWalletID: domain.ValidInt64{ + Value: senderID, + Valid: true, + }, + ReceiverWalletID: domain.ValidInt64{ + Value: receiverID, + Valid: true, + }, + CashierID: cashierID, + Amount: amount, + Type: domain.WALLET, + PaymentMethod: paymentMethod, + Verified: true, }) if err != nil { return domain.Transfer{}, err @@ -206,3 +103,66 @@ func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiver return transfer, nil } + +func (s *Service) SendTransferNotification(ctx context.Context, senderWallet domain.Wallet, receiverWallet domain.Wallet, + senderRole domain.Role, receiverRole domain.Role, amount domain.Currency) error { + // Send notification to sender (this could be any role) that money was transferred + senderWalletReceiverSide := domain.ReceiverFromRole(senderRole) + + senderNotify := &domain.Notification{ + RecipientID: senderWallet.UserID, + Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS, + Level: domain.NotificationLevelSuccess, + Reciever: senderWalletReceiverSide, + DeliveryChannel: domain.DeliveryChannelInApp, + Payload: domain.NotificationPayload{ + Headline: "Wallet has been deducted", + Message: fmt.Sprintf(`ETB %d has been transferred from your wallet`), + }, + Priority: 2, + Metadata: []byte(fmt.Sprintf(`{ + "transfer_amount": %d, + "current_balance": %d, + "wallet_id": %d, + "notification_type": "customer_facing" + }`, amount, senderWallet.Balance, senderWallet.ID)), + } + + // Sender notifications + if err := s.notificationStore.SendNotification(ctx, senderNotify); err != nil { + s.logger.Error("failed to send sender notification", + "user_id", "", + "error", err) + return err + } + + receiverWalletReceiverSide := domain.ReceiverFromRole(receiverRole) + + receiverNotify := &domain.Notification{ + RecipientID: receiverWallet.UserID, + Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS, + Level: domain.NotificationLevelSuccess, + Reciever: receiverWalletReceiverSide, + DeliveryChannel: domain.DeliveryChannelInApp, + Payload: domain.NotificationPayload{ + Headline: "Wallet has been credited", + Message: fmt.Sprintf(`ETB %d has been transferred to your wallet`), + }, + Priority: 2, + Metadata: []byte(fmt.Sprintf(`{ + "transfer_amount": %d, + "current_balance": %d, + "wallet_id": %d, + "notification_type": "customer_facing" + }`, amount, receiverWallet.Balance, receiverWallet.ID)), + } + // Sender notifications + if err := s.notificationStore.SendNotification(ctx, receiverNotify); err != nil { + s.logger.Error("failed to send sender notification", + "user_id", "", + "error", err) + return err + } + + return nil +} diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index cf9cd4c..fb94f86 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -3,6 +3,7 @@ package wallet import ( "context" "errors" + "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) @@ -68,28 +69,152 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu return s.walletStore.UpdateBalance(ctx, id, balance) } -func (s *Service) AddToWallet(ctx context.Context, id int64, amount domain.Currency) error { +func (s *Service) AddToWallet( + 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 err + return domain.Transfer{}, err } - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) + err = s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) + if err != nil { + return domain.Transfer{}, err + } + + // Log the transfer here for reference + newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + Amount: amount, + Verified: true, + ReceiverWalletID: domain.ValidInt64{ + Value: wallet.ID, + Valid: true, + }, + CashierID: cashierID, + PaymentMethod: paymentMethod, + Type: domain.DEPOSIT, + ReferenceNumber: paymentDetails.ReferenceNumber.Value, + }) + + return newTransfer, err } -func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency) error { +func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency, walletType domain.WalletType, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod) (domain.Transfer, error) { wallet, err := s.GetWalletByID(ctx, id) if err != nil { - return err + return domain.Transfer{}, err } if wallet.Balance < amount { - return ErrBalanceInsufficient + // Send Wallet low to admin + if walletType == domain.CompanyWalletType || walletType == domain.BranchWalletType { + s.SendAdminWalletLowNotification(ctx, wallet, amount) + } + return domain.Transfer{}, ErrBalanceInsufficient } - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance-amount) + err = s.walletStore.UpdateBalance(ctx, id, wallet.Balance-amount) + + if err != nil { + return domain.Transfer{}, nil + } + + // Log the transfer here for reference + newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + Amount: amount, + Verified: true, + SenderWalletID: domain.ValidInt64{ + Value: wallet.ID, + Valid: true, + }, + CashierID: cashierID, + PaymentMethod: paymentMethod, + Type: domain.WITHDRAW, + ReferenceNumber: "", + }) + + return newTransfer, err } +// Directly Refilling wallet without +// func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { +// receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID) +// if err != nil { +// return domain.Transfer{}, err +// } + +// // Add to receiver +// senderWallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID) +// if err != nil { +// return domain.Transfer{}, err +// } else if senderWallet.Balance < transfer.Amount { +// return domain.Transfer{}, ErrInsufficientBalance +// } + +// err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount) +// if err != nil { +// return domain.Transfer{}, err +// } + +// // Log the transfer so that if there is a mistake, it can be reverted +// newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ +// CashierID: transfer.CashierID, +// ReceiverWalletID: transfer.ReceiverWalletID, +// Amount: transfer.Amount, +// Type: domain.DEPOSIT, +// PaymentMethod: transfer.PaymentMethod, +// Verified: true, +// }) +// if err != nil { +// return domain.Transfer{}, err +// } + +// return newTransfer, nil +// } + func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { return s.walletStore.UpdateWalletActive(ctx, id, isActive) } + +func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWallet domain.Wallet, amount domain.Currency) error { + // Send notification to admin team + adminNotification := &domain.Notification{ + RecipientID: adminWallet.UserID, + Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT, + Level: domain.NotificationLevelError, + Reciever: domain.NotificationRecieverSideAdmin, + DeliveryChannel: domain.DeliveryChannelEmail, // Or any preferred admin channel + Payload: domain.NotificationPayload{ + Headline: "CREDIT WARNING: System Running Out of Funds", + Message: fmt.Sprintf( + "Wallet ID %d has insufficient balance for transfer. Current balance: %.2f, Attempted transfer: %.2f", + adminWallet.ID, + adminWallet.Balance.Float32(), + amount.Float32(), + ), + }, + Priority: 1, // High priority for admin alerts + Metadata: fmt.Appendf(nil, `{ + "wallet_id": %d, + "balance": %d, + "required_amount": %d, + "notification_type": "admin_alert" + }`, adminWallet.ID, adminWallet.Balance, amount), + } + + // Get admin recipients and send to all + adminRecipients, err := s.notificationStore.ListRecipientIDs(ctx, domain.NotificationRecieverSideAdmin) + if err != nil { + s.logger.Error("failed to get admin recipients", "error", err) + return err + } else { + for _, adminID := range adminRecipients { + adminNotification.RecipientID = adminID + if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { + s.logger.Error("failed to send admin notification", + "admin_id", adminID, + "error", err) + } + } + } + return nil +} diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 9aef6cc..1bce7d2 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -62,30 +62,30 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S // } // }, // }, - { - spec: "0 */5 * * * *", // Every 5 Minutes - task: func() { - log.Println("Updating expired events status...") + // { + // spec: "0 */5 * * * *", // Every 5 Minutes + // task: func() { + // log.Println("Updating expired events status...") - if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { - log.Printf("Failed to update events: %v", err) - } else { - log.Printf("Successfully updated expired events") - } - }, - }, - { - spec: "0 */15 * * * *", // Every 15 Minutes - task: func() { - log.Println("Fetching results for upcoming events...") + // if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { + // log.Printf("Failed to update events: %v", err) + // } else { + // log.Printf("Successfully updated expired events") + // } + // }, + // }, + // { + // spec: "0 */15 * * * *", // Every 15 Minutes + // task: func() { + // log.Println("Fetching results for upcoming events...") - if err := resultService.FetchAndProcessResults(context.Background()); err != nil { - log.Printf("Failed to process result: %v", err) - } else { - log.Printf("Successfully processed all outcomes") - } - }, - }, + // if err := resultService.FetchAndProcessResults(context.Background()); err != nil { + // log.Printf("Failed to process result: %v", err) + // } else { + // log.Printf("Successfully processed all outcomes") + // } + // }, + // }, } for _, job := range schedule { diff --git a/internal/web_server/handlers/auth_handler.go b/internal/web_server/handlers/auth_handler.go index 1b3cc97..8c22fdd 100644 --- a/internal/web_server/handlers/auth_handler.go +++ b/internal/web_server/handlers/auth_handler.go @@ -36,15 +36,14 @@ type loginCustomerRes struct { // @Failure 500 {object} response.APIResponse // @Router /auth/login [post] func (h *Handler) LoginCustomer(c *fiber.Ctx) error { - var req loginCustomerReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse LoginCustomer request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - if valErrs, ok := h.validator.Validate(c, req); !ok { - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + if _, ok := h.validator.Validate(c, req); !ok { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Request") } successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password) diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index a7a0706..7410044 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -1,7 +1,6 @@ package handlers import ( - "fmt" "strconv" "time" @@ -24,7 +23,6 @@ import ( // @Failure 500 {object} response.APIResponse // @Router /bet [post] func (h *Handler) CreateBet(c *fiber.Ctx) error { - fmt.Printf("Calling leagues") // Get user_id from middleware userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) @@ -161,12 +159,29 @@ 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 != "" { + isShopBetParse, err := strconv.ParseBool(isShopBetQuery) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet") + } + isShopBet = domain.ValidBool{ + Value: isShopBetParse, + Valid: true, + } + } bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ BranchID: branchID, CompanyID: companyID, + IsShopBet: isShopBet, }) if err != nil { h.logger.Error("Failed to get bets", "error", err) diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 6f869a1..290f040 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -381,8 +381,23 @@ func (h *Handler) GetBranchByCompanyID(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /branch [get] func (h *Handler) GetAllBranches(c *fiber.Ctx) error { - // TODO: Limit the get all branches to only the companies for branch manager and cashiers - branches, err := h.branchSvc.GetAllBranches(c.Context()) + companyID := c.Locals("company_id").(domain.ValidInt64) + isActiveParam := c.Params("is_active") + isActiveValid := isActiveParam != "" + isActive, err := strconv.ParseBool(isActiveParam) + + if isActiveValid && err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid is_active param", err, nil) + } + + branches, err := h.branchSvc.GetAllBranches(c.Context(), + domain.BranchFilter{ + CompanyID: companyID, + IsSuspended: domain.ValidBool{ + Value: isActive, + Valid: isActiveValid, + }, + }) if err != nil { h.logger.Error("Failed to get branches", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) diff --git a/internal/web_server/handlers/manager.go b/internal/web_server/handlers/manager.go index 948ca05..02c1496 100644 --- a/internal/web_server/handlers/manager.go +++ b/internal/web_server/handlers/manager.go @@ -111,7 +111,8 @@ type ManagersRes struct { func (h *Handler) GetAllManagers(c *fiber.Ctx) error { role := c.Locals("role").(domain.Role) companyId := c.Locals("company_id").(domain.ValidInt64) - + + // Checking to make sure that admin user has a company id in the token if role != domain.RoleSuperAdmin && !companyId.Valid { return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") } @@ -182,32 +183,38 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /managers/{id} [get] func (h *Handler) GetManagerByID(c *fiber.Ctx) error { - // branchId := int64(12) //c.Locals("branch_id").(int64) - // filter := user.Filter{ - // Role: string(domain.RoleUser), - // BranchId: user.ValidBranchId{ - // Value: branchId, - // Valid: true, - // }, - // Page: c.QueryInt("page", 1), - // PageSize: c.QueryInt("page_size", 10), - // } - // valErrs, ok := validator.Validate(c, filter) - // if !ok { - // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - // } + role := c.Locals("role").(domain.Role) + companyId := c.Locals("company_id").(domain.ValidInt64) + requestUserID := c.Locals("user_id").(int64) + + // Only Super Admin / Admin / Branch Manager can view this route + if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager { + return fiber.NewError(fiber.StatusUnauthorized, "Role Unauthorized") + } + + if role != domain.RoleSuperAdmin && !companyId.Valid { + return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") + } userIDstr := c.Params("id") userID, err := strconv.ParseInt(userIDstr, 10, 64) if err != nil { - h.logger.Error("failed to fetch user using UserID", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid managers ID", nil, nil) + return fiber.NewError(fiber.StatusBadRequest, "Invalid managers ID") } user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { - h.logger.Error("Get User By ID failed", "error", err) - return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get managers", nil, nil) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get managers") + } + + // A Branch Manager can only fetch his own branch info + if role == domain.RoleBranchManager && user.ID != requestUserID { + return fiber.NewError(fiber.StatusBadRequest, "User Access Not Allowed") + } + + // Check that only admin from company can view this route + if role != domain.RoleSuperAdmin && user.CompanyID.Value != companyId.Value { + return fiber.NewError(fiber.StatusBadRequest, "Only company user can view manager info") } lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) @@ -259,7 +266,9 @@ type updateManagerReq struct { // @Failure 500 {object} response.APIResponse // @Router /managers/{id} [put] func (h *Handler) UpdateManagers(c *fiber.Ctx) error { + var req updateManagerReq + if err := c.BodyParser(&req); err != nil { h.logger.Error("UpdateManagers failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) diff --git a/internal/web_server/handlers/mongoLogger.go b/internal/web_server/handlers/mongoLogger.go index 384e3a2..f31d780 100644 --- a/internal/web_server/handlers/mongoLogger.go +++ b/internal/web_server/handlers/mongoLogger.go @@ -12,7 +12,7 @@ import ( 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("mongodb://root:secret@localhost:27017/?authSource=admin")) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error()) } @@ -32,7 +32,6 @@ func GetLogsHandler(appCtx context.Context) fiber.Handler { return fiber.NewError(fiber.StatusInternalServerError, "Cursor decoding error: "+err.Error()) } - return c.JSON(logs) } } diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go index b272a39..a2a5a56 100644 --- a/internal/web_server/handlers/transfer_handler.go +++ b/internal/web_server/handlers/transfer_handler.go @@ -15,7 +15,7 @@ type TransferWalletRes struct { Verified bool `json:"verified" example:"true"` Type string `json:"type" example:"transfer"` PaymentMethod string `json:"payment_method" example:"bank"` - ReceiverWalletID int64 `json:"receiver_wallet_id" example:"1"` + ReceiverWalletID *int64 `json:"receiver_wallet_id" example:"1"` SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` CashierID *int64 `json:"cashier_id" example:"789"` CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` @@ -27,7 +27,7 @@ type RefillRes struct { Verified bool `json:"verified" example:"true"` Type string `json:"type" example:"transfer"` PaymentMethod string `json:"payment_method" example:"bank"` - ReceiverWalletID int64 `json:"receiver_wallet_id" example:"1"` + ReceiverWalletID *int64 `json:"receiver_wallet_id" example:"1"` SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` CashierID *int64 `json:"cashier_id" example:"789"` CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` @@ -35,13 +35,20 @@ type RefillRes struct { } func convertTransfer(transfer domain.Transfer) TransferWalletRes { - var senderWalletID *int64 - senderWalletID = &transfer.SenderWalletID var cashierID *int64 if transfer.CashierID.Valid { cashierID = &transfer.CashierID.Value } + var receiverID *int64 + if transfer.ReceiverWalletID.Valid { + receiverID = &transfer.ReceiverWalletID.Value + } + + var senderId *int64 + if transfer.SenderWalletID.Valid { + senderId = &transfer.SenderWalletID.Value + } return TransferWalletRes{ ID: transfer.ID, @@ -49,8 +56,8 @@ func convertTransfer(transfer domain.Transfer) TransferWalletRes { Verified: transfer.Verified, Type: string(transfer.Type), PaymentMethod: string(transfer.PaymentMethod), - ReceiverWalletID: transfer.ReceiverWalletID, - SenderWalletID: senderWalletID, + ReceiverWalletID: receiverID, + SenderWalletID: senderId, CashierID: cashierID, CreatedAt: transfer.CreatedAt, UpdatedAt: transfer.UpdatedAt, @@ -126,16 +133,16 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error { } // Get sender ID from the cashier userID := c.Locals("user_id").(int64) - role := string(c.Locals("role").(domain.Role)) + role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(int64) var senderID int64 //TODO: check to make sure that the cashiers aren't transferring TO branch wallet - if role == string(domain.RoleCustomer) { + if role == domain.RoleCustomer { h.logger.Error("Unauthorized access", "userID", userID, "role", role) return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) - } else if role == string(domain.RoleBranchManager) || role == string(domain.RoleAdmin) || role == string(domain.RoleSuperAdmin) { + } else if role == domain.RoleBranchManager || role == domain.RoleAdmin || role == domain.RoleSuperAdmin { company, err := h.companySvc.GetCompanyByID(c.Context(), companyID) if err != nil { return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching company", err, nil) @@ -190,6 +197,7 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error { receiverIDString := c.Params("id") + userID := c.Locals("user_id").(int64) receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) if err != nil { @@ -197,13 +205,6 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) } // Get sender ID from the cashier - userID := c.Locals("user_id").(int64) - role := string(c.Locals("role").(domain.Role)) - - if role != string(domain.RoleSuperAdmin) { - h.logger.Error("Unauthorized access", "userID", userID, "role", role) - return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) - } var req CreateRefillReq @@ -217,16 +218,11 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - transfer, err := h.walletSvc.RefillWallet(c.Context(), domain.CreateTransfer{ - Amount: domain.ToCurrency(req.Amount), - PaymentMethod: domain.TRANSFER_BANK, - ReceiverWalletID: receiverID, - CashierID: domain.ValidInt64{ + transfer, err := h.walletSvc.AddToWallet( + c.Context(), receiverID, domain.ToCurrency(req.Amount), domain.ValidInt64{ Value: userID, Valid: true, - }, - Type: domain.TransferType("deposit"), - }) + }, domain.TRANSFER_BANK, domain.PaymentDetails{}) if !ok { return response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 522551c..57fed45 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -192,7 +192,8 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { } // TODO: Remove later - err = h.walletSvc.AddToWallet(c.Context(), newWallet.ID, domain.ToCurrency(100.0)) + _, err = h.walletSvc.AddToWallet( + c.Context(), newWallet.ID, 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) @@ -413,6 +414,7 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { return nil } companyID := c.Locals("company_id").(domain.ValidInt64) + users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID) if err != nil { h.logger.Error("SearchUserByNameOrPhone failed", "error", err) diff --git a/internal/web_server/middleware.go b/internal/web_server/middleware.go index 3a6303d..47dabc3 100644 --- a/internal/web_server/middleware.go +++ b/internal/web_server/middleware.go @@ -87,6 +87,22 @@ func (a *App) CompanyOnly(c *fiber.Ctx) error { return c.Next() } +func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error { + userRole := c.Locals("role").(domain.Role) + if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") + } + return c.Next() +} + +func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error { + userRole := c.Locals("role").(domain.Role) + if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin && userRole != domain.RoleBranchManager { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") + } + return c.Next() +} + func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error { tokenStr := c.Query("token") if tokenStr == "" { diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index d40c932..7b17b28 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -189,7 +189,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("/cashierWallet", a.authMiddleware, h.GetWalletForCashier) +a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier) // Transfer // /transfer/wallet - transfer from one wallet to another wallet diff --git a/logs/app.log b/logs/app.log index e69de29..c5454cd 100644 --- a/logs/app.log +++ b/logs/app.log @@ -0,0 +1,2 @@ +time=2025-06-16T02:21:34.859+03:00 level=INFO msg="Authenticated WebSocket connection" service_info.env=development userID=3 +time=2025-06-16T02:23:59.721+03:00 level=INFO msg="Starting server" service_info.env=development port=8080