more veli-games endpoints

This commit is contained in:
Yared Yemane 2025-06-28 12:47:12 +03:00
commit bd0859d3ad
47 changed files with 868 additions and 212 deletions

View File

@ -44,6 +44,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "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/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -98,6 +99,7 @@ func main() {
v := customvalidator.NewCustomValidator(validator.New()) v := customvalidator.NewCustomValidator(validator.New())
// Initialize services // Initialize services
settingSvc := settings.NewService(store)
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry) authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
userSvc := user.NewService(store, store, cfg) userSvc := user.NewService(store, store, cfg)
eventSvc := event.New(cfg.Bet365Token, store) eventSvc := event.New(cfg.Bet365Token, store)
@ -119,7 +121,7 @@ func main() {
branchSvc := branch.NewService(store) branchSvc := branch.NewService(store)
companySvc := company.NewService(store) companySvc := company.NewService(store)
leagueSvc := league.New(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) betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc) resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
referalRepo := repository.NewReferralRepository(store) referalRepo := repository.NewReferralRepository(store)
@ -232,6 +234,7 @@ func main() {
currSvc, currSvc,
cfg.Port, cfg.Port,
v, v,
settingSvc,
authSvc, authSvc,
logger, logger,
jwtutil.JwtConfig{ jwtutil.JwtConfig{

View File

@ -303,7 +303,8 @@ CREATE VIEW branch_details AS
SELECT branches.*, SELECT branches.*,
CONCAT(users.first_name, ' ', users.last_name) AS manager_name, CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
users.phone_number AS manager_phone_number, users.phone_number AS manager_phone_number,
wallets.balance wallets.balance,
wallets.is_active AS wallet_is_active
FROM branches FROM branches
LEFT JOIN users ON branches.branch_manager_id = users.id LEFT JOIN users ON branches.branch_manager_id = users.id
LEFT JOin wallets ON wallets.id = branches.wallet_id; LEFT JOin wallets ON wallets.id = branches.wallet_id;
@ -324,6 +325,25 @@ SELECT tickets.*,
FROM tickets FROM tickets
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id
GROUP BY tickets.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 -- Foreign Keys
ALTER TABLE users ALTER TABLE users
ADD CONSTRAINT unique_email UNIQUE (email), ADD CONSTRAINT unique_email UNIQUE (email),

View File

@ -1,5 +1,17 @@
-- Settings Initial Data -- Settings Initial Data
INSERT INTO settings (key, value) 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 VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
UPDATE UPDATE
SET value = EXCLUDED.value; SET value = EXCLUDED.value;

View File

@ -63,6 +63,19 @@ wHERE (
AND ( AND (
is_shop_bet = sqlc.narg('is_shop_bet') is_shop_bet = sqlc.narg('is_shop_bet')
OR sqlc.narg('is_shop_bet') IS NULL 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 -- name: GetBetByID :one
SELECT * SELECT *
@ -83,7 +96,13 @@ WHERE user_id = $1;
-- name: GetBetOutcomeByEventID :many -- name: GetBetOutcomeByEventID :many
SELECT * SELECT *
FROM bet_outcomes 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 -- name: GetBetOutcomeByBetID :many
SELECT * SELECT *
FROM bet_outcomes FROM bet_outcomes
@ -103,6 +122,11 @@ UPDATE bet_outcomes
SET status = $1 SET status = $1
WHERE id = $2 WHERE id = $2
RETURNING *; RETURNING *;
-- name: UpdateBetOutcomeStatusByBetID :one
UPDATE bet_outcomes
SET status = $1
WHERE bet_id = $2
RETURNING *;
-- name: UpdateBetOutcomeStatusForEvent :many -- name: UpdateBetOutcomeStatusForEvent :many
UPDATE bet_outcomes UPDATE bet_outcomes
SEt status = $1 SEt status = $1
@ -118,6 +142,4 @@ DELETE FROM bets
WHERE id = $1; WHERE id = $1;
-- name: DeleteBetOutcome :exec -- name: DeleteBetOutcome :exec
DELETE FROM bet_outcomes DELETE FROM bet_outcomes
WHERE bet_id = $1; WHERE bet_id = $1;

View File

@ -31,6 +31,23 @@ WHERE (
AND ( AND (
is_active = sqlc.narg('is_active') is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL 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 -- name: GetBranchByID :one
SELECT * SELECT *

View File

@ -1,6 +1,10 @@
-- name: GetSettings :many -- name: GetSettings :many
SELECT * SELECT *
from settings; FROM settings;
-- name: GetSetting :one
SELECT *
FROM settings
WHERE key = $1;
-- name: SaveSetting :one -- name: SaveSetting :one
INSERT INTO settings (key, value, updated_at) INSERT INTO settings (key, value, updated_at)
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO

View File

@ -26,20 +26,13 @@ WHERE id = $1;
SELECT * SELECT *
FROM wallets FROM wallets
WHERE user_id = $1; WHERE user_id = $1;
-- name: GetAllCustomerWallet :many
SELECT *
FROM customer_wallet_details;
-- name: GetCustomerWallet :one -- name: GetCustomerWallet :one
SELECT cw.id, SELECT *
cw.customer_id, FROM customer_wallet_details
rw.id AS regular_id, WHERE customer_id = $1;
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;
-- name: GetAllBranchWallets :many -- name: GetAllBranchWallets :many
SELECT wallets.id, SELECT wallets.id,
wallets.balance, wallets.balance,

View File

@ -133,13 +133,29 @@ wHERE (
is_shop_bet = $4 is_shop_bet = $4
OR $4 IS NULL 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 { type GetAllBetsParams struct {
BranchID pgtype.Int8 `json:"branch_id"` BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
UserID pgtype.Int8 `json:"user_id"` UserID pgtype.Int8 `json:"user_id"`
IsShopBet pgtype.Bool `json:"is_shop_bet"` 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) { 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.CompanyID,
arg.UserID, arg.UserID,
arg.IsShopBet, arg.IsShopBet,
arg.Query,
arg.CreatedBefore,
arg.CreatedAfter,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -394,11 +413,23 @@ func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetO
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many 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 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 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) { type GetBetOutcomeByEventIDParams struct {
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID) 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 { if err != nil {
return nil, err return nil, err
} }
@ -468,6 +499,41 @@ func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutco
return i, err 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 const UpdateBetOutcomeStatusForEvent = `-- name: UpdateBetOutcomeStatusForEvent :many
UPDATE bet_outcomes UPDATE bet_outcomes
SEt status = $1 SEt status = $1

View File

@ -155,7 +155,7 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
} }
const GetAllBranches = `-- name: GetAllBranches :many 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 FROM branch_details
WHERE ( WHERE (
company_id = $1 company_id = $1
@ -165,15 +165,43 @@ WHERE (
is_active = $2 is_active = $2
OR $2 IS NULL 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 { type GetAllBranchesParams struct {
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
IsActive pgtype.Bool `json:"is_active"` 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -195,6 +223,7 @@ func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams)
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance, &i.Balance,
&i.WalletIsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -257,7 +286,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
} }
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many 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 FROM branch_details
WHERE company_id = $1 WHERE company_id = $1
` `
@ -285,6 +314,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance, &i.Balance,
&i.WalletIsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -297,7 +327,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
} }
const GetBranchByID = `-- name: GetBranchByID :one 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 FROM branch_details
WHERE id = $1 WHERE id = $1
` `
@ -319,12 +349,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance, &i.Balance,
&i.WalletIsActive,
) )
return i, err return i, err
} }
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many 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 FROM branch_details
WHERE branch_manager_id = $1 WHERE branch_manager_id = $1
` `
@ -352,6 +383,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance, &i.Balance,
&i.WalletIsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -411,7 +443,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
} }
const SearchBranchByName = `-- name: SearchBranchByName :many 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 FROM branch_details
WHERE name ILIKE '%' || $1 || '%' WHERE name ILIKE '%' || $1 || '%'
` `
@ -439,6 +471,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text)
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance, &i.Balance,
&i.WalletIsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }

View File

@ -161,6 +161,7 @@ type BranchDetail struct {
ManagerName interface{} `json:"manager_name"` ManagerName interface{} `json:"manager_name"`
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"` ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
Balance pgtype.Int8 `json:"balance"` Balance pgtype.Int8 `json:"balance"`
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
} }
type BranchOperation struct { type BranchOperation struct {
@ -199,6 +200,23 @@ type CustomerWallet struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` 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 { type Event struct {
ID string `json:"id"` ID string `json:"id"`
SportID pgtype.Int4 `json:"sport_id"` SportID pgtype.Int4 `json:"sport_id"`

View File

@ -9,9 +9,27 @@ import (
"context" "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 const GetSettings = `-- name: GetSettings :many
SELECT key, value, created_at, updated_at SELECT key, value, created_at, updated_at
from settings FROM settings
` `
func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) { func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) {

View File

@ -143,6 +143,46 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet
return items, nil 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 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 SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
FROM wallets FROM wallets
@ -226,36 +266,14 @@ func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Com
} }
const GetCustomerWallet = `-- name: GetCustomerWallet :one const GetCustomerWallet = `-- name: GetCustomerWallet :one
SELECT cw.id, 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
cw.customer_id, FROM customer_wallet_details
rw.id AS regular_id, WHERE customer_id = $1
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
` `
type GetCustomerWalletRow struct { func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (CustomerWalletDetail, error) {
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) {
row := q.db.QueryRow(ctx, GetCustomerWallet, customerID) row := q.db.QueryRow(ctx, GetCustomerWallet, customerID)
var i GetCustomerWalletRow var i CustomerWalletDetail
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CustomerID, &i.CustomerID,
@ -263,9 +281,14 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC
&i.RegularBalance, &i.RegularBalance,
&i.StaticID, &i.StaticID,
&i.StaticBalance, &i.StaticBalance,
&i.RegularIsActive,
&i.StaticIsActive,
&i.RegularUpdatedAt, &i.RegularUpdatedAt,
&i.StaticUpdatedAt, &i.StaticUpdatedAt,
&i.CreatedAt, &i.CreatedAt,
&i.FirstName,
&i.LastName,
&i.PhoneNumber,
) )
return i, err return i, err
} }

View File

@ -57,10 +57,13 @@ type Bet struct {
} }
type BetFilter struct { type BetFilter struct {
BranchID ValidInt64 // Can Be Nullable BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable UserID ValidInt64 // Can Be Nullable
IsShopBet ValidBool IsShopBet ValidBool
Query ValidString
CreatedBefore ValidTime
CreatedAfter ValidTime
} }
type GetBet struct { type GetBet struct {

View File

@ -7,13 +7,17 @@ type Branch struct {
WalletID int64 WalletID int64
BranchManagerID int64 BranchManagerID int64
CompanyID int64 CompanyID int64
IsSuspended bool IsActive bool
IsSelfOwned bool IsSelfOwned bool
} }
type BranchFilter struct { type BranchFilter struct {
CompanyID ValidInt64 CompanyID ValidInt64
IsSuspended ValidBool IsActive ValidBool
BranchManagerID ValidInt64
Query ValidString
CreatedBefore ValidTime
CreatedAfter ValidTime
} }
type BranchDetail struct { type BranchDetail struct {
@ -24,10 +28,11 @@ type BranchDetail struct {
Balance Currency Balance Currency
BranchManagerID int64 BranchManagerID int64
CompanyID int64 CompanyID int64
IsSuspended bool IsActive bool
IsSelfOwned bool IsSelfOwned bool
ManagerName string ManagerName string
ManagerPhoneNumber string ManagerPhoneNumber string
WalletIsActive bool
} }
type SupportedOperation struct { type SupportedOperation struct {
@ -58,7 +63,7 @@ type UpdateBranch struct {
BranchManagerID *int64 BranchManagerID *int64
CompanyID *int64 CompanyID *int64
IsSelfOwned *bool IsSelfOwned *bool
IsActive *bool IsActive *bool
} }
type CreateSupportedOperation struct { type CreateSupportedOperation struct {

View File

@ -29,7 +29,6 @@ type LeagueFilter struct {
Offset ValidInt64 Offset ValidInt64
} }
// These leagues are automatically featured when the league is created // These leagues are automatically featured when the league is created
var FeaturedLeagues = []int64{ var FeaturedLeagues = []int64{
// Football // Football
@ -46,6 +45,7 @@ var FeaturedLeagues = []int64{
10050346, //UEFA Super Cup 10050346, //UEFA Super Cup
10081269, //CONCACAF Champions Cup 10081269, //CONCACAF Champions Cup
10070189, //CONCACAF Gold Cup 10070189, //CONCACAF Gold Cup
10076185, //UEFA Regions Cup
10067913, //Europe - World Cup Qualifying 10067913, //Europe - World Cup Qualifying
10040162, //Asia - World Cup Qualifying 10040162, //Asia - World Cup Qualifying

View File

@ -13,3 +13,10 @@ type SettingRes struct {
Value string `json:"value"` Value string `json:"value"`
UpdatedAt string `json:"updated_at"` 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"`
}

View File

@ -15,6 +15,13 @@ type Wallet struct {
CreatedAt time.Time CreatedAt time.Time
} }
type WalletFilter struct {
IsActive ValidBool
Query ValidString
CreatedBefore ValidTime
CreatedAfter ValidTime
}
type CustomerWallet struct { type CustomerWallet struct {
ID int64 ID int64
RegularID int64 RegularID int64
@ -28,9 +35,14 @@ type GetCustomerWallet struct {
StaticID int64 StaticID int64
StaticBalance Currency StaticBalance Currency
CustomerID int64 CustomerID int64
RegularIsActive bool
StaticIsActive bool
RegularUpdatedAt time.Time RegularUpdatedAt time.Time
StaticUpdatedAt time.Time StaticUpdatedAt time.Time
CreatedAt time.Time CreatedAt time.Time
FirstName string
LastName string
PhoneNumber string
} }
type BranchWallet struct { type BranchWallet struct {

View File

@ -213,6 +213,18 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
Bool: filter.IsShopBet.Value, Bool: filter.IsShopBet.Value,
Valid: filter.IsShopBet.Valid, 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 { if err != nil {
domain.MongoDBLogger.Error("failed to get all bets", 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 return err
} }
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) { func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
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 { if err != nil {
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID", domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
zap.Int64("event_id", eventID), zap.Int64("event_id", eventID),
@ -364,6 +387,24 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom
return res, nil 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) { func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{ outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{
EventID: eventID, EventID: eventID,
@ -386,10 +427,6 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6
return result, nil return result, nil
} }
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
return s.queries.DeleteBet(ctx, id)
}
// GetBetSummary returns aggregated bet statistics // GetBetSummary returns aggregated bet statistics
func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) ( func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
totalStakes domain.Currency, totalStakes domain.Currency,

View File

@ -32,6 +32,8 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
ManagerName: dbBranch.ManagerName.(string), ManagerName: dbBranch.ManagerName.(string),
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String, ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
Balance: domain.Currency(dbBranch.Balance.Int64), 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, Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid, 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 { if err != nil {
return nil, err return nil, err

View File

@ -341,7 +341,7 @@ func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain
ID: dbBranch.ID, ID: dbBranch.ID,
Name: dbBranch.Name, Name: dbBranch.Name,
Location: dbBranch.Location, Location: dbBranch.Location,
IsSuspended: dbBranch.IsActive, IsActive: dbBranch.IsActive,
WalletID: dbBranch.WalletID, WalletID: dbBranch.WalletID,
BranchManagerID: dbBranch.BranchManagerID, BranchManagerID: dbBranch.BranchManagerID,
CompanyID: dbBranch.CompanyID, CompanyID: dbBranch.CompanyID,

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"strconv"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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 { func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Referral) error {
rewardAmount := pgtype.Numeric{} rewardAmount := pgtype.Numeric{}
if err := rewardAmount.Scan(referral.RewardAmount); err != nil { if err := rewardAmount.Scan(strconv.Itoa(int(referral.RewardAmount))); err != nil {
return err return err
} }

View File

@ -2,12 +2,74 @@ package repository
import ( import (
"context" "context"
"fmt"
"strconv"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"go.uber.org/zap" "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) { func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
settings, err := s.queries.GetSettings(ctx) settings, err := s.queries.GetSettings(ctx)
@ -27,9 +89,25 @@ func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
return result, nil 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) { func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{ dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{
Key: key, Key: key,
Value: value, Value: value,
}) })
@ -40,7 +118,7 @@ func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Sett
} }
setting := domain.Setting{ setting := domain.Setting{
Key: dbSetting.Key, Key: dbSetting.Key,
Value: dbSetting.Value, Value: dbSetting.Value,
} }

View File

@ -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{ return domain.GetCustomerWallet{
ID: customerWallet.ID, ID: customerWallet.ID,
RegularID: customerWallet.RegularID, RegularID: customerWallet.RegularID,
@ -55,9 +55,14 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai
StaticID: customerWallet.StaticID, StaticID: customerWallet.StaticID,
StaticBalance: domain.Currency(customerWallet.StaticBalance), StaticBalance: domain.Currency(customerWallet.StaticBalance),
CustomerID: customerWallet.CustomerID, CustomerID: customerWallet.CustomerID,
RegularIsActive: customerWallet.RegularIsActive,
StaticIsActive: customerWallet.StaticIsActive,
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time, RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time, StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time,
CreatedAt: customerWallet.CreatedAt.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 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) { func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID) customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)

View File

@ -15,14 +15,14 @@ type BetStore interface {
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
GetBetByUserID(ctx context.Context, UserID 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) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error) GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, 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) 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) ( GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
totalStakes domain.Currency, totalStakes domain.Currency,

View File

@ -30,10 +30,11 @@ var (
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending") ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
ErrEventHasBeenRemoved = errors.New("Event has been removed") ErrEventHasBeenRemoved = errors.New("Event has been removed")
ErrEventHasNotEnded = errors.New("Event has not ended yet") ErrEventHasNotEnded = errors.New("Event has not ended yet")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid") ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
ErrBranchIDRequired = errors.New("Branch ID required for this role") ErrBranchIDRequired = errors.New("Branch ID required for this role")
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet") ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
) )
type Service struct { type Service struct {
@ -326,7 +327,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
newBet.IsShopBet = true newBet.IsShopBet = true
case domain.RoleCustomer: case domain.RoleCustomer:
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get customer wallets", s.mongoLogger.Error("failed to get customer wallets",
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
@ -334,17 +335,52 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
) )
return domain.CreateBetRes{}, err 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} newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
@ -838,8 +874,23 @@ func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID in
return outcomes, nil return outcomes, nil
} }
func (s *Service) DeleteBet(ctx context.Context, id int64) error { func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error {
return s.betStore.DeleteBet(ctx, id) _, 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) { func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {

View File

@ -215,9 +215,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
for sportIndex, sportID := range sportIDs { for sportIndex, sportID := range sportIDs {
var totalPages int = 1 var totalPages int = 1
var page int = 0 var page int = 0
var limit int = 1 var limit int = 200
var count int = 0 var count int = 0
log.Printf("Sport ID %d", sportID)
for page <= totalPages { for page <= totalPages {
page = page + 1 page = page + 1
url := fmt.Sprintf(url, sportID, s.token, page) url := fmt.Sprintf(url, sportID, s.token, page)

View File

@ -1,4 +1,4 @@
package odds package odds
import ( import (
"context" "context"

View File

@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base32" "encoding/base32"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"strconv" "strconv"
"time" "time"
@ -53,15 +54,23 @@ func (s *Service) GenerateReferralCode() (string, error) {
func (s *Service) CreateReferral(ctx context.Context, userID int64) error { func (s *Service) CreateReferral(ctx context.Context, userID int64) error {
s.logger.Info("Creating referral code for user", "userID", userID) s.logger.Info("Creating referral code for user", "userID", userID)
// TODO: check in user already has an active referral code
code, err := s.GenerateReferralCode() code, err := s.GenerateReferralCode()
if err != nil { if err != nil {
s.logger.Error("Failed to generate referral code", "error", err) s.logger.Error("Failed to generate referral code", "error", err)
return err return err
} }
if err := s.repo.UpdateUserReferalCode(ctx, domain.UpdateUserReferalCode{ // TODO: get the referral settings from db
UserID: userID, var rewardAmount float64 = 100
Code: code, 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 { }); err != nil {
return err return err
} }
@ -73,12 +82,12 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode) s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode)
referral, err := s.repo.GetReferralByCode(ctx, 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) s.logger.Error("Failed to get referral by code", "referralCode", referralCode, "error", err)
return 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) s.logger.Warn("Invalid or expired referral", "referralCode", referralCode, "status", referral.Status)
return ErrInvalidReferral return ErrInvalidReferral
} }
@ -106,27 +115,21 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
return err return err
} }
referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) referrerId, err := strconv.Atoi(referral.ReferrerID)
if err != nil { if err != nil {
s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err) s.logger.Error("Failed to convert referrer id", "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)
return err return err
} }
if len(wallets) == 0 {
s.logger.Error("Referrer has no wallet", "referrerID", referrerID) wallets, err := s.store.GetCustomerWallet(ctx, int64(referrerId))
return errors.New("referrer has no wallet") if err != nil {
s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err)
return err
} }
walletID := wallets[0].ID _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
currentBonus := float64(wallets[0].Balance)
_, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
if err != nil { 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 return err
} }

View File

@ -52,7 +52,7 @@ var (
func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, resultRes json.RawMessage, sportID int64) error { 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 // 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 { if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err) 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) 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 { 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 { if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err) s.logger.Error("Failed to get pending bet outcomes", "error", err)

View File

@ -7,6 +7,8 @@ import (
) )
type SettingStore interface { type SettingStore interface {
GetSettingList(ctx context.Context) (domain.SettingList, error)
GetSettings(ctx context.Context) ([]domain.Setting, 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) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error)
} }

View File

@ -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) { func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) {
return s.settingStore.GetSettings(ctx) 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) { func (s *Service) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
return s.settingStore.SaveSetting(ctx, key, value) return s.settingStore.SaveSetting(ctx, key, value)
} }

View File

@ -11,13 +11,14 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"go.uber.org/zap" "go.uber.org/zap"
) )
var ( var (
// ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events") // ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
// ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending") // 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") ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
ErrEventHasBeenRemoved = errors.New("Event has been removed") ErrEventHasBeenRemoved = errors.New("Event has been removed")
ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket") ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket")
@ -33,6 +34,7 @@ type Service struct {
eventSvc event.Service eventSvc event.Service
prematchSvc odds.ServiceImpl prematchSvc odds.ServiceImpl
mongoLogger *zap.Logger mongoLogger *zap.Logger
settingSvc settings.Service
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
} }
@ -41,6 +43,7 @@ func NewService(
eventSvc event.Service, eventSvc event.Service,
prematchSvc odds.ServiceImpl, prematchSvc odds.ServiceImpl,
mongoLogger *zap.Logger, mongoLogger *zap.Logger,
settingSvc settings.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
) *Service { ) *Service {
return &Service{ return &Service{
@ -48,11 +51,12 @@ func NewService(
eventSvc: eventSvc, eventSvc: eventSvc,
prematchSvc: prematchSvc, prematchSvc: prematchSvc,
mongoLogger: mongoLogger, mongoLogger: mongoLogger,
settingSvc: settingSvc,
notificationSvc: notificationSvc, 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) eventIDStr := strconv.FormatInt(eventID, 10)
marketIDStr := strconv.FormatInt(marketID, 10) marketIDStr := strconv.FormatInt(marketID, 10)
oddIDStr := strconv.FormatInt(oddID, 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("event_start_time", event.StartTime),
zap.Time("current_time", currentTime), zap.Time("current_time", currentTime),
) )
return domain.CreateTicketOutcome{}, ErrEventHasNotEnded return domain.CreateTicketOutcome{}, ErrTicketHasExpired
} }
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr) 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) { 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") // Check to see if the number of outcomes is above a set limit
// TODO Validate Outcomes Here and make sure they didn't expire if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) {
// Validation for creating tickets
if len(req.Outcomes) > 30 {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket
} }
if req.Amount > 100000 { // Check to see if the amount is above a set limit
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil) if req.Amount > settingsList.BetAmountLimit.Float32() {
return domain.Ticket{}, 0, ErrTicketAmountTooHigh return domain.Ticket{}, 0, ErrTicketAmountTooHigh
} }
count, err := s.CountTicketByIP(ctx, clientIP) count, err := s.CountTicketByIP(ctx, clientIP)
if err != nil { 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 return domain.Ticket{}, 0, err
} }
if count > 50 { // Check to see how many tickets a single anonymous user has created
// return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil) if count > settingsList.DailyTicketPerIP {
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
} }
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
var totalOdds float32 = 1 var totalOdds float32 = 1
for _, outcomeReq := range req.Outcomes { 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 { if err != nil {
s.mongoLogger.Error("failed to generate outcome", s.mongoLogger.Error("failed to generate outcome",
zap.Int64("event_id", outcomeReq.EventID), zap.Int64("event_id", outcomeReq.EventID),
@ -198,9 +204,13 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
outcomes = append(outcomes, newOutcome) outcomes = append(outcomes, newOutcome)
} }
totalWinnings := req.Amount * totalOdds totalWinnings := req.Amount * totalOdds
if totalWinnings > 1000000 {
s.mongoLogger.Error("Total Winnings over limit", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount)) // Check to see if the total winning amount is over a set limit
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil) 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 return domain.Ticket{}, 0, ErrTicketWinningTooHigh
} }

View File

@ -14,6 +14,7 @@ type WalletStore interface {
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
GetAllWallets(ctx context.Context) ([]domain.Wallet, error) GetAllWallets(ctx context.Context) ([]domain.Wallet, error)
GetWalletsByUser(ctx context.Context, id int64) ([]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) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error)
GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error)
UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error

View File

@ -57,6 +57,9 @@ func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wall
return s.walletStore.GetWalletsByUser(ctx, id) 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) { func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
return s.walletStore.GetCustomerWallet(ctx, customerID) return s.walletStore.GetCustomerWallet(ctx, customerID)
} }

View File

@ -20,6 +20,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "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/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -50,6 +51,7 @@ type App struct {
NotidicationStore *notificationservice.Service NotidicationStore *notificationservice.Service
referralSvc referralservice.ReferralStore referralSvc referralservice.ReferralStore
port int port int
settingSvc *settings.Service
authSvc *authentication.Service authSvc *authentication.Service
userSvc *user.Service userSvc *user.Service
betSvc *bet.Service betSvc *bet.Service
@ -76,6 +78,7 @@ func NewApp(
instSvc *institutions.Service, instSvc *institutions.Service,
currSvc *currency.Service, currSvc *currency.Service,
port int, validator *customvalidator.CustomValidator, port int, validator *customvalidator.CustomValidator,
settingSvc *settings.Service,
authSvc *authentication.Service, authSvc *authentication.Service,
logger *slog.Logger, logger *slog.Logger,
JwtConfig jwtutil.JwtConfig, JwtConfig jwtutil.JwtConfig,
@ -118,9 +121,11 @@ func NewApp(
s := &App{ s := &App{
issueReportingSvc: issueReportingSvc, issueReportingSvc: issueReportingSvc,
instSvc: instSvc, instSvc: instSvc,
currSvc: currSvc, currSvc: currSvc,
fiber: app, fiber: app,
port: port, port: port,
settingSvc: settingSvc,
authSvc: authSvc, authSvc: authSvc,
validator: validator, validator: validator,
logger: logger, logger: logger,

View File

@ -23,22 +23,22 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
spec string spec string
task func() task func()
}{ }{
// { {
// spec: "0 0 * * * *", // Every 1 hour spec: "0 0 * * * *", // Every 1 hour
// task: func() { task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
// log.Printf("FetchUpcomingEvents error: %v", err) log.Printf("FetchUpcomingEvents error: %v", err)
// } }
// }, },
// }, },
// { {
// spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events)
// task: func() { task: func() {
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
// log.Printf("FetchNonLiveOdds error: %v", err) log.Printf("FetchNonLiveOdds error: %v", err)
// } }
// }, },
// }, },
{ {
spec: "0 */5 * * * *", // Every 5 Minutes spec: "0 */5 * * * *", // Every 5 Minutes
task: func() { task: func() {
@ -66,7 +66,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
job.task() // job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err) log.Fatalf("Failed to schedule cron job: %v", err)
} }

View File

@ -209,13 +209,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet [get] // @Router /sport/bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error { func (h *Handler) GetAllBet(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64) branchID := c.Locals("branch_id").(domain.ValidInt64)
var isShopBet domain.ValidBool var isShopBet domain.ValidBool
isShopBetQuery := c.Query("is_shop") isShopBetQuery := c.Query("is_shop")
if isShopBetQuery != "" && role == domain.RoleSuperAdmin {
if isShopBetQuery != "" {
isShopBetParse, err := strconv.ParseBool(isShopBetQuery) isShopBetParse, err := strconv.ParseBool(isShopBetQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to parse is_shop_bet", 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{ bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
BranchID: branchID, BranchID: branchID,
CompanyID: companyID, CompanyID: companyID,
IsShopBet: isShopBet, IsShopBet: isShopBet,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
}) })
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get bets", 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") 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 { if err != nil {
h.mongoLoggerSvc.Error("Failed to delete bet by ID", h.mongoLoggerSvc.Error("Failed to delete bet by ID",
zap.Int64("betID", id), zap.Int64("betID", id),

View File

@ -3,6 +3,7 @@ package handlers
import ( import (
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
@ -56,6 +57,7 @@ type BranchRes struct {
BranchManagerID int64 `json:"branch_manager_id" example:"1"` BranchManagerID int64 `json:"branch_manager_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"` CompanyID int64 `json:"company_id" example:"1"`
IsSelfOwned bool `json:"is_self_owned" example:"false"` IsSelfOwned bool `json:"is_self_owned" example:"false"`
IsActive bool `json:"is_active" example:"false"`
} }
type BranchDetailRes struct { type BranchDetailRes struct {
@ -69,6 +71,8 @@ type BranchDetailRes struct {
ManagerName string `json:"manager_name" example:"John Smith"` ManagerName string `json:"manager_name" example:"John Smith"`
ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"` ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"`
Balance float32 `json:"balance" example:"100.5"` 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 { func convertBranch(branch domain.Branch) BranchRes {
@ -80,6 +84,7 @@ func convertBranch(branch domain.Branch) BranchRes {
BranchManagerID: branch.BranchManagerID, BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID, CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned, IsSelfOwned: branch.IsSelfOwned,
IsActive: branch.IsActive,
} }
} }
@ -95,6 +100,8 @@ func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes {
ManagerName: branch.ManagerName, ManagerName: branch.ManagerName,
ManagerPhoneNumber: branch.ManagerPhoneNumber, ManagerPhoneNumber: branch.ManagerPhoneNumber,
Balance: branch.Balance.Float32(), 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) 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(), branches, err := h.branchSvc.GetAllBranches(c.Context(),
domain.BranchFilter{ domain.BranchFilter{
CompanyID: companyID, CompanyID: companyID,
IsSuspended: domain.ValidBool{ IsActive: domain.ValidBool{
Value: isActive, Value: isActive,
Valid: isActiveValid, Valid: isActiveValid,
}, },
BranchManagerID: branchManagerID,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
}) })
if err != nil { if err != nil {
h.logger.Error("Failed to get branches", "error", err) h.logger.Error("Failed to get branches", "error", err)

View File

@ -184,7 +184,34 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
res := convertGetCompany(company) res := convertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) 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 // GetAllCompanies godoc

View File

@ -20,6 +20,7 @@ import (
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "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/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -37,6 +38,7 @@ type Handler struct {
instSvc *institutions.Service instSvc *institutions.Service
currSvc *currency.Service currSvc *currency.Service
logger *slog.Logger logger *slog.Logger
settingSvc *settings.Service
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
userSvc *user.Service userSvc *user.Service
referralSvc referralservice.ReferralStore referralSvc referralservice.ReferralStore
@ -68,6 +70,7 @@ func New(
instSvc *institutions.Service, instSvc *institutions.Service,
currSvc *currency.Service, currSvc *currency.Service,
logger *slog.Logger, logger *slog.Logger,
settingSvc *settings.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
validator *customvalidator.CustomValidator, validator *customvalidator.CustomValidator,
reportSvc report.ReportStore, reportSvc report.ReportStore,
@ -98,6 +101,7 @@ func New(
instSvc: instSvc, instSvc: instSvc,
currSvc: currSvc, currSvc: currSvc,
logger: logger, logger: logger,
settingSvc: settingSvc,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
reportSvc: reportSvc, reportSvc: reportSvc,
chapaSvc: chapaSvc, chapaSvc: chapaSvc,

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"context" "context"
"os"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -20,7 +21,7 @@ import (
// @Router /api/v1/logs [get] // @Router /api/v1/logs [get]
func GetLogsHandler(appCtx context.Context) fiber.Handler { func GetLogsHandler(appCtx context.Context) fiber.Handler {
return func(c *fiber.Ctx) error { 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 { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error())
} }

View File

@ -177,9 +177,11 @@ type TopLeaguesRes struct {
} }
type TopLeague struct { type TopLeague struct {
LeagueID int64 `json:"league_id"` LeagueID int64 `json:"league_id"`
LeagueName string `json:"league_name"` LeagueName string `json:"league_name"`
Events []domain.UpcomingEvent `json:"events"` LeagueCC string `json:"league_cc"`
LeagueSportID int32 `json:"league_sport_id"`
Events []domain.UpcomingEvent `json:"events"`
// Total int64 `json:"total"` // Total int64 `json:"total"`
} }
@ -188,7 +190,7 @@ type TopLeague struct {
// @Tags prematch // @Tags prematch
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {array} domain.UpcomingEvent // @Success 200 {array} TopLeague
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /top-leagues [get] // @Router /top-leagues [get]
func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { 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) 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) h.logger.Error("Error while fetching events for top league", "League ID", league.ID)
} }
topLeague = append(topLeague, TopLeague{ topLeague = append(topLeague, TopLeague{
LeagueID: league.ID, LeagueID: league.ID,
LeagueName: league.Name, LeagueName: league.Name,
Events: events, LeagueCC: league.CountryCode,
LeagueSportID: league.SportID,
Events: events,
}) })
} }

View File

@ -35,7 +35,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
if err != nil { if err != nil {
switch err { 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.StatusBadRequest, err.Error())
} }
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return fiber.NewError(fiber.StatusInternalServerError, err.Error())

View File

@ -59,9 +59,8 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
} }
type RegisterCodeReq struct { type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// SendRegisterCode godoc // 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") 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) h.logger.Error("Failed to send register code", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") 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 { type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"` LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"` Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"` ReferalCode string `json:"referal_code" example:"ABC123"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// RegisterUser godoc // RegisterUser godoc
@ -176,11 +174,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error") return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error")
} }
newWallet, err := h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID)
UserID: newUser.ID,
IsWithdraw: true,
IsBettable: true,
})
if err != nil { if err != nil {
h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err) h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet") 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 // TODO: Remove later
_, err = h.walletSvc.AddToWallet( _, 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 { if err != nil {
h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err) 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 { type ResetCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` // Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// SendResetCode godoc // 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") 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) h.logger.Error("Failed to send reset code", "error", err)
fmt.Println(err) fmt.Println(err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")

View File

@ -9,9 +9,6 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
type WalletRes struct { type WalletRes struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
Balance float32 `json:"amount" example:"100.0"` Balance float32 `json:"amount" example:"100.0"`
@ -45,9 +42,14 @@ type CustomerWalletRes struct {
StaticID int64 `json:"static_id" example:"1"` StaticID int64 `json:"static_id" example:"1"`
StaticBalance float32 `json:"static_balance" example:"100.0"` StaticBalance float32 `json:"static_balance" example:"100.0"`
CustomerID int64 `json:"customer_id" example:"1"` 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"` RegularUpdatedAt time.Time `json:"regular_updated_at"`
StaticUpdatedAt time.Time `json:"static_updated_at"` StaticUpdatedAt time.Time `json:"static_updated_at"`
CreatedAt time.Time `json:"created_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 { func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
@ -58,9 +60,14 @@ func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
StaticID: wallet.StaticID, StaticID: wallet.StaticID,
StaticBalance: wallet.StaticBalance.Float32(), StaticBalance: wallet.StaticBalance.Float32(),
CustomerID: wallet.CustomerID, CustomerID: wallet.CustomerID,
RegularIsActive: wallet.RegularIsActive,
StaticIsActive: wallet.StaticIsActive,
RegularUpdatedAt: wallet.RegularUpdatedAt, RegularUpdatedAt: wallet.RegularUpdatedAt,
StaticUpdatedAt: wallet.StaticUpdatedAt, StaticUpdatedAt: wallet.StaticUpdatedAt,
CreatedAt: wallet.CreatedAt, 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) 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 // UpdateWalletActive godoc
@ -255,13 +293,13 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
// h.logger.Info("Fetching customer wallet", "userID", userID) // 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 { if err != nil {
h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err) h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet") 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) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
} }

View File

@ -24,6 +24,7 @@ func (a *App) initAppRoutes() {
a.instSvc, a.instSvc,
a.currSvc, a.currSvc,
a.logger, a.logger,
a.settingSvc,
a.NotidicationStore, a.NotidicationStore,
a.validator, a.validator,
a.reportSvc, a.reportSvc,
@ -55,7 +56,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/", func(c *fiber.Ctx) error { a.fiber.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"message": "Welcome to the FortuneBet API", "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.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany)
a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID) a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID)
a.fiber.Get("/search/company", a.authMiddleware, h.SearchCompany) a.fiber.Get("/search/company", a.authMiddleware, h.SearchCompany)
a.fiber.Get("/admin-company", a.authMiddleware, h.GetCompanyForAdmin)
// Ticket Routes // Ticket Routes
a.fiber.Post("/ticket", h.CreateTicket) a.fiber.Post("/ticket", h.CreateTicket)
@ -196,6 +198,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/wallet/:id", h.GetWalletByID) a.fiber.Get("/wallet/:id", h.GetWalletByID)
a.fiber.Put("/wallet/:id", h.UpdateWalletActive) a.fiber.Put("/wallet/:id", h.UpdateWalletActive)
a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets) a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets)
a.fiber.Get("/customerWallet", a.authMiddleware, h.GetAllCustomerWallets)
a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier) a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
// Transfer // Transfer

View File

@ -58,4 +58,4 @@ db-down:
@docker volume rm fortunebet-backend_postgres_data @docker volume rm fortunebet-backend_postgres_data
.PHONY: sqlc-gen .PHONY: sqlc-gen
sqlc-gen: sqlc-gen:
@sqlc generate @sqlc generate