diff --git a/cmd/main.go b/cmd/main.go index 8553aaa..00bc4a3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -44,6 +44,7 @@ import ( referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -98,6 +99,7 @@ func main() { v := customvalidator.NewCustomValidator(validator.New()) // Initialize services + settingSvc := settings.NewService(store) authSvc := authentication.NewService(store, store, cfg.RefreshExpiry) userSvc := user.NewService(store, store, cfg) eventSvc := event.New(cfg.Bet365Token, store) @@ -119,7 +121,7 @@ func main() { branchSvc := branch.NewService(store) companySvc := company.NewService(store) leagueSvc := league.New(store) - ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, notificationSvc) + ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc) betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger) resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc) referalRepo := repository.NewReferralRepository(store) @@ -232,6 +234,7 @@ func main() { currSvc, cfg.Port, v, + settingSvc, authSvc, logger, jwtutil.JwtConfig{ diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 29f9c7f..6ed5000 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -303,7 +303,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; @@ -324,6 +325,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/migrations/000007_setting_data.up.sql b/db/migrations/000007_setting_data.up.sql index d01ab65..f4cdfd6 100644 --- a/db/migrations/000007_setting_data.up.sql +++ b/db/migrations/000007_setting_data.up.sql @@ -1,5 +1,17 @@ -- Settings Initial Data INSERT INTO settings (key, value) +VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO +UPDATE +SET value = EXCLUDED.value; +INSERT INTO settings (key, value) +VALUES ('bet_amount_limit', '100000') ON CONFLICT (key) DO +UPDATE +SET value = EXCLUDED.value; +INSERT INTO settings (key, value) +VALUES ('daily_ticket_limit', '50') ON CONFLICT (key) DO +UPDATE +SET value = EXCLUDED.value; +INSERT INTO settings (key, value) VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; \ No newline at end of file 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/settings.sql b/db/query/settings.sql index f8a1e31..d0f4482 100644 --- a/db/query/settings.sql +++ b/db/query/settings.sql @@ -1,6 +1,10 @@ -- name: GetSettings :many SELECT * -from settings; +FROM settings; +-- name: GetSetting :one +SELECT * +FROM settings +WHERE key = $1; -- name: SaveSetting :one INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO diff --git a/db/query/wallet.sql b/db/query/wallet.sql index 3d6169b..79028e5 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 2f7457c..3ba6c5e 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -161,6 +161,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 { @@ -199,6 +200,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/settings.sql.go b/gen/db/settings.sql.go index 352966a..d842661 100644 --- a/gen/db/settings.sql.go +++ b/gen/db/settings.sql.go @@ -9,9 +9,27 @@ import ( "context" ) +const GetSetting = `-- name: GetSetting :one +SELECT key, value, created_at, updated_at +FROM settings +WHERE key = $1 +` + +func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) { + row := q.db.QueryRow(ctx, GetSetting, key) + var i Setting + err := row.Scan( + &i.Key, + &i.Value, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const GetSettings = `-- name: GetSettings :many SELECT key, value, created_at, updated_at -from settings +FROM settings ` func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) { diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 212ee99..3a49ebf 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 @@ -226,36 +266,14 @@ func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Com } 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, @@ -263,9 +281,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/league.go b/internal/domain/league.go index ffaceb4..c4a2d12 100644 --- a/internal/domain/league.go +++ b/internal/domain/league.go @@ -29,7 +29,6 @@ type LeagueFilter struct { Offset ValidInt64 } - // These leagues are automatically featured when the league is created var FeaturedLeagues = []int64{ // Football @@ -46,6 +45,7 @@ var FeaturedLeagues = []int64{ 10050346, //UEFA Super Cup 10081269, //CONCACAF Champions Cup 10070189, //CONCACAF Gold Cup + 10076185, //UEFA Regions Cup 10067913, //Europe - World Cup Qualifying 10040162, //Asia - World Cup Qualifying diff --git a/internal/domain/settings.go b/internal/domain/settings.go index 083f915..c0c8368 100644 --- a/internal/domain/settings.go +++ b/internal/domain/settings.go @@ -13,3 +13,10 @@ type SettingRes struct { Value string `json:"value"` UpdatedAt string `json:"updated_at"` } + +type SettingList struct { + MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` + BetAmountLimit Currency `json:"bet_amount_limit"` + DailyTicketPerIP int64 `json:"daily_ticket_limit"` + TotalWinningLimit Currency `json:"total_winning_limit"` +} 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/notification.go b/internal/repository/notification.go index c7fb7e9..c279377 100644 --- a/internal/repository/notification.go +++ b/internal/repository/notification.go @@ -341,7 +341,7 @@ func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain ID: dbBranch.ID, Name: dbBranch.Name, Location: dbBranch.Location, - IsSuspended: dbBranch.IsActive, + IsActive: dbBranch.IsActive, WalletID: dbBranch.WalletID, BranchManagerID: dbBranch.BranchManagerID, CompanyID: dbBranch.CompanyID, diff --git a/internal/repository/referal.go b/internal/repository/referal.go index 274acd9..a782cfb 100644 --- a/internal/repository/referal.go +++ b/internal/repository/referal.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "strconv" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -44,7 +45,7 @@ func (r *ReferralRepo) UpdateUserReferalCode(ctx context.Context, codedata domai func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Referral) error { rewardAmount := pgtype.Numeric{} - if err := rewardAmount.Scan(referral.RewardAmount); err != nil { + if err := rewardAmount.Scan(strconv.Itoa(int(referral.RewardAmount))); err != nil { return err } diff --git a/internal/repository/settings.go b/internal/repository/settings.go index 3bf0c8e..7cf0d29 100644 --- a/internal/repository/settings.go +++ b/internal/repository/settings.go @@ -2,12 +2,74 @@ package repository import ( "context" + "fmt" + "strconv" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "go.uber.org/zap" ) +type DBSettingList struct { + MaxNumberOfOutcomes domain.ValidInt64 + BetAmountLimit domain.ValidInt64 + DailyTicketPerIP domain.ValidInt64 + TotalWinningLimit domain.ValidInt64 +} + +func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) { + var dbSettingList DBSettingList + var int64SettingsMap = map[string]*domain.ValidInt64{ + "max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes, + "bet_amount_limit": &dbSettingList.BetAmountLimit, + "daily_ticket_limit": &dbSettingList.DailyTicketPerIP, + "total_winnings_limit": &dbSettingList.TotalWinningLimit, + } + + for _, setting := range settings { + is_setting_unknown := true + for key, dbSetting := range int64SettingsMap { + if setting.Key == key { + value, err := strconv.ParseInt(setting.Value, 10, 64) + if err != nil { + return domain.SettingList{}, err + } + *dbSetting = domain.ValidInt64{ + Value: value, + Valid: true, + } + is_setting_unknown = false + } + } + + if is_setting_unknown { + domain.MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) + } + } + + for key, dbSetting := range int64SettingsMap { + if !dbSetting.Valid { + fmt.Printf("setting value not found on database: %v \n", key) + domain.MongoDBLogger.Warn("setting value not found on database", zap.String("setting", key)) + } + } + + return domain.SettingList{ + MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value, + BetAmountLimit: domain.Currency(dbSettingList.BetAmountLimit.Value), + DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value, + TotalWinningLimit: domain.Currency(dbSettingList.TotalWinningLimit.Value), + }, nil +} +func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) { + settings, err := s.queries.GetSettings(ctx) + if err != nil { + domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err)) + } + + return GetDBSettingList(settings) +} + func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) { settings, err := s.queries.GetSettings(ctx) @@ -27,9 +89,25 @@ func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) { return result, nil } +func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, error) { + dbSetting, err := s.queries.GetSetting(ctx, key) + + if err != nil { + domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err)) + } + + result := domain.Setting{ + Key: dbSetting.Key, + Value: dbSetting.Value, + UpdatedAt: dbSetting.UpdatedAt.Time, + } + + return result, nil +} + func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) { dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{ - Key: key, + Key: key, Value: value, }) @@ -40,7 +118,7 @@ func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Sett } setting := domain.Setting{ - Key: dbSetting.Key, + Key: dbSetting.Key, Value: dbSetting.Value, } diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 7007be9..4a6ae45 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/event/service.go b/internal/services/event/service.go index 7833df7..592241c 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -215,9 +215,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour for sportIndex, sportID := range sportIDs { var totalPages int = 1 var page int = 0 - var limit int = 1 + var limit int = 200 var count int = 0 - log.Printf("Sport ID %d", sportID) for page <= totalPages { page = page + 1 url := fmt.Sprintf(url, sportID, s.token, page) diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index b3010d0..cdfda1e 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -1,4 +1,4 @@ -package odds + package odds import ( "context" diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index bbb0d43..5585d74 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/base32" "errors" + "fmt" "log/slog" "strconv" "time" @@ -53,15 +54,23 @@ func (s *Service) GenerateReferralCode() (string, error) { func (s *Service) CreateReferral(ctx context.Context, userID int64) error { s.logger.Info("Creating referral code for user", "userID", userID) + // TODO: check in user already has an active referral code code, err := s.GenerateReferralCode() if err != nil { s.logger.Error("Failed to generate referral code", "error", err) return err } - if err := s.repo.UpdateUserReferalCode(ctx, domain.UpdateUserReferalCode{ - UserID: userID, - Code: code, + // TODO: get the referral settings from db + var rewardAmount float64 = 100 + var expireDuration time.Time = time.Now().Add(24 * time.Hour) + + if err := s.repo.CreateReferral(ctx, &domain.Referral{ + ReferralCode: code, + ReferrerID: fmt.Sprintf("%d", userID), + Status: domain.ReferralPending, + RewardAmount: rewardAmount, + ExpiresAt: expireDuration, }); err != nil { return err } @@ -73,12 +82,12 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode) referral, err := s.repo.GetReferralByCode(ctx, referralCode) - if err != nil { + if err != nil || referral == nil { s.logger.Error("Failed to get referral by code", "referralCode", referralCode, "error", err) return err } - if referral == nil || referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) { + if referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) { s.logger.Warn("Invalid or expired referral", "referralCode", referralCode, "status", referral.Status) return ErrInvalidReferral } @@ -106,27 +115,21 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo return err } - referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) + referrerId, err := strconv.Atoi(referral.ReferrerID) if err != nil { - s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err) - return errors.New("invalid referrer phone number format") - } - - wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) - if err != nil { - s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err) + s.logger.Error("Failed to convert referrer id", "referrerId", referral.ReferrerID, "error", err) return err } - if len(wallets) == 0 { - s.logger.Error("Referrer has no wallet", "referrerID", referrerID) - return errors.New("referrer has no wallet") + + wallets, err := s.store.GetCustomerWallet(ctx, int64(referrerId)) + if err != nil { + s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err) + return err } - walletID := wallets[0].ID - currentBonus := float64(wallets[0].Balance) - _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(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) + s.logger.Error("Failed to add referral reward to static wallet", "walletID", wallets.StaticID, "referrer phone number", referredPhone, "error", err) return err } 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/settings/port.go b/internal/services/settings/port.go index 587805a..ce86f06 100644 --- a/internal/services/settings/port.go +++ b/internal/services/settings/port.go @@ -7,6 +7,8 @@ import ( ) type SettingStore interface { + GetSettingList(ctx context.Context) (domain.SettingList, error) GetSettings(ctx context.Context) ([]domain.Setting, error) + GetSetting(ctx context.Context, key string) (domain.Setting, error) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) -} +} diff --git a/internal/services/settings/service.go b/internal/services/settings/service.go index 95f0083..66591a1 100644 --- a/internal/services/settings/service.go +++ b/internal/services/settings/service.go @@ -16,10 +16,17 @@ func NewService(settingStore SettingStore) *Service { } } +func (s *Service) GetSettingList(ctx context.Context) (domain.SettingList, error) { + return s.settingStore.GetSettingList(ctx) +} + func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) { return s.settingStore.GetSettings(ctx) } +func (s *Service) GetSetting(ctx context.Context, key string) (domain.Setting, error) { + return s.settingStore.GetSetting(ctx, key) +} func (s *Service) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) { return s.settingStore.SaveSetting(ctx, key, value) } diff --git a/internal/services/ticket/service.go b/internal/services/ticket/service.go index 00b3fab..6a91b7d 100644 --- a/internal/services/ticket/service.go +++ b/internal/services/ticket/service.go @@ -11,13 +11,14 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "go.uber.org/zap" ) var ( // ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events") // ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending") - ErrEventHasNotEnded = errors.New("Event has not ended yet") + ErrTicketHasExpired = errors.New("Ticket has expired") ErrNoEventsAvailable = errors.New("Not enough events available with the given filters") ErrEventHasBeenRemoved = errors.New("Event has been removed") ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket") @@ -33,6 +34,7 @@ type Service struct { eventSvc event.Service prematchSvc odds.ServiceImpl mongoLogger *zap.Logger + settingSvc settings.Service notificationSvc *notificationservice.Service } @@ -41,6 +43,7 @@ func NewService( eventSvc event.Service, prematchSvc odds.ServiceImpl, mongoLogger *zap.Logger, + settingSvc settings.Service, notificationSvc *notificationservice.Service, ) *Service { return &Service{ @@ -48,11 +51,12 @@ func NewService( eventSvc: eventSvc, prematchSvc: prematchSvc, mongoLogger: mongoLogger, + settingSvc: settingSvc, notificationSvc: notificationSvc, } } -func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) { +func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.SettingList, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) { eventIDStr := strconv.FormatInt(eventID, 10) marketIDStr := strconv.FormatInt(marketID, 10) oddIDStr := strconv.FormatInt(oddID, 10) @@ -73,7 +77,7 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, mark zap.Time("event_start_time", event.StartTime), zap.Time("current_time", currentTime), ) - return domain.CreateTicketOutcome{}, ErrEventHasNotEnded + return domain.CreateTicketOutcome{}, ErrTicketHasExpired } odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr) @@ -155,36 +159,38 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, mark } func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) { + settingsList, err := s.settingSvc.GetSettingList(ctx) - // s.mongoLogger.Info("Creating ticket") - // TODO Validate Outcomes Here and make sure they didn't expire - // Validation for creating tickets - if len(req.Outcomes) > 30 { - // return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) + // Check to see if the number of outcomes is above a set limit + if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) { return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket } - if req.Amount > 100000 { - // return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil) + // Check to see if the amount is above a set limit + if req.Amount > settingsList.BetAmountLimit.Float32() { return domain.Ticket{}, 0, ErrTicketAmountTooHigh } count, err := s.CountTicketByIP(ctx, clientIP) if err != nil { - // return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil) + s.mongoLogger.Error("failed to count number of ticket using ip", + zap.Error(err), + ) return domain.Ticket{}, 0, err } - if count > 50 { - // return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil) + // Check to see how many tickets a single anonymous user has created + if count > settingsList.DailyTicketPerIP { + return domain.Ticket{}, 0, ErrTicketLimitForSingleUser } + var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) var totalOdds float32 = 1 for _, outcomeReq := range req.Outcomes { - newOutcome, err := s.GenerateTicketOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) + newOutcome, err := s.GenerateTicketOutcome(ctx, settingsList, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) if err != nil { s.mongoLogger.Error("failed to generate outcome", zap.Int64("event_id", outcomeReq.EventID), @@ -198,9 +204,13 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, outcomes = append(outcomes, newOutcome) } totalWinnings := req.Amount * totalOdds - if totalWinnings > 1000000 { - s.mongoLogger.Error("Total Winnings over limit", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount)) - // return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil) + + // Check to see if the total winning amount is over a set limit + if totalWinnings > settingsList.TotalWinningLimit.Float32() { + s.mongoLogger.Error("Total Winnings over limit", + zap.Float32("Total Odds", totalOdds), + zap.Float32("amount", req.Amount), + zap.Float32("limit", settingsList.TotalWinningLimit.Float32())) return domain.Ticket{}, 0, ErrTicketWinningTooHigh } diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index 9ba436c..fb761cb 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -14,6 +14,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 911febf..eab19f1 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) } diff --git a/internal/web_server/app.go b/internal/web_server/app.go index 787770c..61bc682 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -20,6 +20,7 @@ import ( referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -50,6 +51,7 @@ type App struct { NotidicationStore *notificationservice.Service referralSvc referralservice.ReferralStore port int + settingSvc *settings.Service authSvc *authentication.Service userSvc *user.Service betSvc *bet.Service @@ -76,6 +78,7 @@ func NewApp( instSvc *institutions.Service, currSvc *currency.Service, port int, validator *customvalidator.CustomValidator, + settingSvc *settings.Service, authSvc *authentication.Service, logger *slog.Logger, JwtConfig jwtutil.JwtConfig, @@ -118,9 +121,11 @@ func NewApp( s := &App{ issueReportingSvc: issueReportingSvc, instSvc: instSvc, - currSvc: currSvc, - fiber: app, - port: port, + currSvc: currSvc, + fiber: app, + port: port, + + settingSvc: settingSvc, authSvc: authSvc, validator: validator, logger: logger, diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 7dfda9d..58e9e24 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -23,22 +23,22 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S spec string task func() }{ - // { - // spec: "0 0 * * * *", // Every 1 hour - // task: func() { - // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { - // log.Printf("FetchUpcomingEvents error: %v", err) - // } - // }, - // }, - // { - // spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) - // task: func() { - // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { - // log.Printf("FetchNonLiveOdds error: %v", err) - // } - // }, - // }, + { + spec: "0 0 * * * *", // Every 1 hour + task: func() { + if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + log.Printf("FetchUpcomingEvents error: %v", err) + } + }, + }, + { + spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) + task: func() { + if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + log.Printf("FetchNonLiveOdds error: %v", err) + } + }, + }, { spec: "0 */5 * * * *", // Every 5 Minutes task: func() { @@ -66,7 +66,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S } for _, job := range schedule { - job.task() + // job.task() if _, err := c.AddFunc(job.spec, job.task); err != nil { log.Fatalf("Failed to schedule cron job: %v", err) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 6ba2ec5..77950e2 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 /sport/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/handlers.go b/internal/web_server/handlers/handlers.go index 4023e8a..db38918 100644 --- a/internal/web_server/handlers/handlers.go +++ b/internal/web_server/handlers/handlers.go @@ -20,6 +20,7 @@ import ( referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -37,6 +38,7 @@ type Handler struct { instSvc *institutions.Service currSvc *currency.Service logger *slog.Logger + settingSvc *settings.Service notificationSvc *notificationservice.Service userSvc *user.Service referralSvc referralservice.ReferralStore @@ -68,6 +70,7 @@ func New( instSvc *institutions.Service, currSvc *currency.Service, logger *slog.Logger, + settingSvc *settings.Service, notificationSvc *notificationservice.Service, validator *customvalidator.CustomValidator, reportSvc report.ReportStore, @@ -98,6 +101,7 @@ func New( instSvc: instSvc, currSvc: currSvc, logger: logger, + settingSvc: settingSvc, notificationSvc: notificationSvc, reportSvc: reportSvc, chapaSvc: chapaSvc, 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/prematch.go b/internal/web_server/handlers/prematch.go index 314bf0f..fbdf594 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -177,9 +177,11 @@ type TopLeaguesRes struct { } type TopLeague struct { - LeagueID int64 `json:"league_id"` - LeagueName string `json:"league_name"` - Events []domain.UpcomingEvent `json:"events"` + LeagueID int64 `json:"league_id"` + LeagueName string `json:"league_name"` + LeagueCC string `json:"league_cc"` + LeagueSportID int32 `json:"league_sport_id"` + Events []domain.UpcomingEvent `json:"events"` // Total int64 `json:"total"` } @@ -188,7 +190,7 @@ type TopLeague struct { // @Tags prematch // @Accept json // @Produce json -// @Success 200 {array} domain.UpcomingEvent +// @Success 200 {array} TopLeague // @Failure 500 {object} response.APIResponse // @Router /top-leagues [get] func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { @@ -213,11 +215,12 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { fmt.Printf("Error while fetching events for top league %v \n", league.ID) h.logger.Error("Error while fetching events for top league", "League ID", league.ID) } - topLeague = append(topLeague, TopLeague{ - LeagueID: league.ID, - LeagueName: league.Name, - Events: events, + LeagueID: league.ID, + LeagueName: league.Name, + LeagueCC: league.CountryCode, + LeagueSportID: league.SportID, + Events: events, }) } diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index e665162..17e6276 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -35,7 +35,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error { if err != nil { switch err { - case ticket.ErrEventHasBeenRemoved, ticket.ErrEventHasNotEnded, ticket.ErrRawOddInvalid: + case ticket.ErrEventHasBeenRemoved, ticket.ErrTicketHasExpired, + ticket.ErrRawOddInvalid, ticket.ErrTooManyOutcomesForTicket, + ticket.ErrTicketAmountTooHigh, ticket.ErrTicketLimitForSingleUser, + ticket.ErrTicketWinningTooHigh: return fiber.NewError(fiber.StatusBadRequest, err.Error()) } return fiber.NewError(fiber.StatusInternalServerError, err.Error()) diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index aa72107..a088698 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -59,9 +59,8 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { } type RegisterCodeReq struct { - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" example:"1234567890"` } // SendRegisterCode godoc @@ -99,7 +98,7 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") } - if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, req.Provider); err != nil { + if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, "twilio"); err != nil { h.logger.Error("Failed to send register code", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") } @@ -108,14 +107,13 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { } type RegisterUserReq struct { - FirstName string `json:"first_name" example:"John"` - LastName string `json:"last_name" example:"Doe"` - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - Password string `json:"password" example:"password123"` - Otp string `json:"otp" example:"123456"` - ReferalCode string `json:"referal_code" example:"ABC123"` - Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + Password string `json:"password" example:"password123"` + Otp string `json:"otp" example:"123456"` + ReferalCode string `json:"referal_code" example:"ABC123"` } // RegisterUser godoc @@ -176,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") @@ -195,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) @@ -206,9 +200,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { } type ResetCodeReq struct { - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` - Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` + // Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` } // SendResetCode godoc @@ -246,7 +240,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") } - if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, req.Provider); err != nil { + if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, "twilio"); err != nil { h.logger.Error("Failed to send reset code", "error", err) fmt.Println(err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") 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 8ebab26..b7b0aca 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -24,6 +24,7 @@ func (a *App) initAppRoutes() { a.instSvc, a.currSvc, a.logger, + a.settingSvc, a.NotidicationStore, a.validator, a.reportSvc, @@ -55,7 +56,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Welcome to the FortuneBet API", - "version": "1.0dev6", + "version": "1.0dev7", }) }) @@ -175,6 +176,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) @@ -196,6 +198,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 diff --git a/makefile b/makefile index 3f32fcf..0f0dcec 100644 --- a/makefile +++ b/makefile @@ -58,4 +58,4 @@ db-down: @docker volume rm fortunebet-backend_postgres_data .PHONY: sqlc-gen sqlc-gen: - @sqlc generate \ No newline at end of file + @sqlc generate