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

View File

@ -303,7 +303,8 @@ CREATE VIEW branch_details AS
SELECT branches.*,
CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
users.phone_number AS manager_phone_number,
wallets.balance
wallets.balance,
wallets.is_active AS wallet_is_active
FROM branches
LEFT JOIN users ON branches.branch_manager_id = users.id
LEFT JOin wallets ON wallets.id = branches.wallet_id;
@ -324,6 +325,25 @@ SELECT tickets.*,
FROM tickets
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id
GROUP BY tickets.id;
CREATE VIEW customer_wallet_details AS
SELECT cw.id,
cw.customer_id,
rw.id AS regular_id,
rw.balance AS regular_balance,
sw.id AS static_id,
sw.balance AS static_balance,
rw.is_active as regular_is_active,
sw.is_active as static_is_active,
rw.updated_at as regular_updated_at,
sw.updated_at as static_updated_at,
cw.created_at,
users.first_name,
users.last_name,
users.phone_number
FROM customer_wallets cw
JOIN wallets rw ON cw.regular_wallet_id = rw.id
JOIN wallets sw ON cw.static_wallet_id = sw.id
JOIN users ON users.id = cw.customer_id;
-- Foreign Keys
ALTER TABLE users
ADD CONSTRAINT unique_email UNIQUE (email),

View File

@ -1,5 +1,17 @@
-- Settings Initial Data
INSERT INTO settings (key, value)
VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('bet_amount_limit', '100000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('daily_ticket_limit', '50') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;

View File

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

View File

@ -31,6 +31,23 @@ WHERE (
AND (
is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
AND (
branch_manager_id = sqlc.narg('branch_manager_id')
OR sqlc.narg('branch_manager_id') IS NULL
)
AND (
name ILIKE '%' || sqlc.narg('query') || '%'
OR location ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
created_at > sqlc.narg('created_before')
OR sqlc.narg('created_before') IS NULL
)
AND (
created_at < sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL
);
-- name: GetBranchByID :one
SELECT *

View File

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

View File

@ -26,20 +26,13 @@ WHERE id = $1;
SELECT *
FROM wallets
WHERE user_id = $1;
-- name: GetAllCustomerWallet :many
SELECT *
FROM customer_wallet_details;
-- name: GetCustomerWallet :one
SELECT cw.id,
cw.customer_id,
rw.id AS regular_id,
rw.balance AS regular_balance,
sw.id AS static_id,
sw.balance AS static_balance,
rw.updated_at as regular_updated_at,
sw.updated_at as static_updated_at,
cw.created_at
FROM customer_wallets cw
JOIN wallets rw ON cw.regular_wallet_id = rw.id
JOIN wallets sw ON cw.static_wallet_id = sw.id
WHERE cw.customer_id = $1;
SELECT *
FROM customer_wallet_details
WHERE customer_id = $1;
-- name: GetAllBranchWallets :many
SELECT wallets.id,
wallets.balance,

View File

@ -133,13 +133,29 @@ wHERE (
is_shop_bet = $4
OR $4 IS NULL
)
AND (
full_name ILIKE '%' || $5 || '%'
OR phone_number ILIKE '%' || $5 || '%'
OR $5 IS NULL
)
AND (
created_at > $6
OR $6 IS NULL
)
AND (
created_at < $7
OR $7 IS NULL
)
`
type GetAllBetsParams struct {
BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"`
UserID pgtype.Int8 `json:"user_id"`
IsShopBet pgtype.Bool `json:"is_shop_bet"`
BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"`
UserID pgtype.Int8 `json:"user_id"`
IsShopBet pgtype.Bool `json:"is_shop_bet"`
Query pgtype.Text `json:"query"`
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
}
func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) {
@ -148,6 +164,9 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
arg.CompanyID,
arg.UserID,
arg.IsShopBet,
arg.Query,
arg.CreatedBefore,
arg.CreatedAfter,
)
if err != nil {
return nil, err
@ -394,11 +413,23 @@ func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetO
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
FROM bet_outcomes
WHERE event_id = $1
WHERE (event_id = $1)
AND (
status = $2
OR $2 IS NULL
OR status = $3
OR $3 IS NULL
)
`
func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]BetOutcome, error) {
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID)
type GetBetOutcomeByEventIDParams struct {
EventID int64 `json:"event_id"`
FilterStatus pgtype.Int4 `json:"filter_status"`
FilterStatus2 pgtype.Int4 `json:"filter_status_2"`
}
func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeByEventIDParams) ([]BetOutcome, error) {
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, arg.EventID, arg.FilterStatus, arg.FilterStatus2)
if err != nil {
return nil, err
}
@ -468,6 +499,41 @@ func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutco
return i, err
}
const UpdateBetOutcomeStatusByBetID = `-- name: UpdateBetOutcomeStatusByBetID :one
UPDATE bet_outcomes
SET status = $1
WHERE bet_id = $2
RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
`
type UpdateBetOutcomeStatusByBetIDParams struct {
Status int32 `json:"status"`
BetID int64 `json:"bet_id"`
}
func (q *Queries) UpdateBetOutcomeStatusByBetID(ctx context.Context, arg UpdateBetOutcomeStatusByBetIDParams) (BetOutcome, error) {
row := q.db.QueryRow(ctx, UpdateBetOutcomeStatusByBetID, arg.Status, arg.BetID)
var i BetOutcome
err := row.Scan(
&i.ID,
&i.BetID,
&i.SportID,
&i.EventID,
&i.OddID,
&i.HomeTeamName,
&i.AwayTeamName,
&i.MarketID,
&i.MarketName,
&i.Odd,
&i.OddName,
&i.OddHeader,
&i.OddHandicap,
&i.Status,
&i.Expires,
)
return i, err
}
const UpdateBetOutcomeStatusForEvent = `-- name: UpdateBetOutcomeStatusForEvent :many
UPDATE bet_outcomes
SEt status = $1

View File

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

View File

@ -161,6 +161,7 @@ type BranchDetail struct {
ManagerName interface{} `json:"manager_name"`
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
Balance pgtype.Int8 `json:"balance"`
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
}
type BranchOperation struct {
@ -199,6 +200,23 @@ type CustomerWallet struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type CustomerWalletDetail struct {
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
RegularID int64 `json:"regular_id"`
RegularBalance int64 `json:"regular_balance"`
StaticID int64 `json:"static_id"`
StaticBalance int64 `json:"static_balance"`
RegularIsActive bool `json:"regular_is_active"`
StaticIsActive bool `json:"static_is_active"`
RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"`
StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"`
CreatedAt pgtype.Timestamp `json:"created_at"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
PhoneNumber pgtype.Text `json:"phone_number"`
}
type Event struct {
ID string `json:"id"`
SportID pgtype.Int4 `json:"sport_id"`

View File

@ -9,9 +9,27 @@ import (
"context"
)
const GetSetting = `-- name: GetSetting :one
SELECT key, value, created_at, updated_at
FROM settings
WHERE key = $1
`
func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) {
row := q.db.QueryRow(ctx, GetSetting, key)
var i Setting
err := row.Scan(
&i.Key,
&i.Value,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetSettings = `-- name: GetSettings :many
SELECT key, value, created_at, updated_at
from settings
FROM settings
`
func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) {

View File

@ -143,6 +143,46 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet
return items, nil
}
const GetAllCustomerWallet = `-- name: GetAllCustomerWallet :many
SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number
FROM customer_wallet_details
`
func (q *Queries) GetAllCustomerWallet(ctx context.Context) ([]CustomerWalletDetail, error) {
rows, err := q.db.Query(ctx, GetAllCustomerWallet)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CustomerWalletDetail
for rows.Next() {
var i CustomerWalletDetail
if err := rows.Scan(
&i.ID,
&i.CustomerID,
&i.RegularID,
&i.RegularBalance,
&i.StaticID,
&i.StaticBalance,
&i.RegularIsActive,
&i.StaticIsActive,
&i.RegularUpdatedAt,
&i.StaticUpdatedAt,
&i.CreatedAt,
&i.FirstName,
&i.LastName,
&i.PhoneNumber,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetAllWallets = `-- name: GetAllWallets :many
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
FROM wallets
@ -226,36 +266,14 @@ func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Com
}
const GetCustomerWallet = `-- name: GetCustomerWallet :one
SELECT cw.id,
cw.customer_id,
rw.id AS regular_id,
rw.balance AS regular_balance,
sw.id AS static_id,
sw.balance AS static_balance,
rw.updated_at as regular_updated_at,
sw.updated_at as static_updated_at,
cw.created_at
FROM customer_wallets cw
JOIN wallets rw ON cw.regular_wallet_id = rw.id
JOIN wallets sw ON cw.static_wallet_id = sw.id
WHERE cw.customer_id = $1
SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number
FROM customer_wallet_details
WHERE customer_id = $1
`
type GetCustomerWalletRow struct {
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
RegularID int64 `json:"regular_id"`
RegularBalance int64 `json:"regular_balance"`
StaticID int64 `json:"static_id"`
StaticBalance int64 `json:"static_balance"`
RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"`
StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}
func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetCustomerWalletRow, error) {
func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (CustomerWalletDetail, error) {
row := q.db.QueryRow(ctx, GetCustomerWallet, customerID)
var i GetCustomerWalletRow
var i CustomerWalletDetail
err := row.Scan(
&i.ID,
&i.CustomerID,
@ -263,9 +281,14 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC
&i.RegularBalance,
&i.StaticID,
&i.StaticBalance,
&i.RegularIsActive,
&i.StaticIsActive,
&i.RegularUpdatedAt,
&i.StaticUpdatedAt,
&i.CreatedAt,
&i.FirstName,
&i.LastName,
&i.PhoneNumber,
)
return i, err
}

View File

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

View File

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

View File

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

View File

@ -13,3 +13,10 @@ type SettingRes struct {
Value string `json:"value"`
UpdatedAt string `json:"updated_at"`
}
type SettingList struct {
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
BetAmountLimit Currency `json:"bet_amount_limit"`
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
TotalWinningLimit Currency `json:"total_winning_limit"`
}

View File

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

View File

@ -213,6 +213,18 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
Bool: filter.IsShopBet.Value,
Valid: filter.IsShopBet.Valid,
},
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
CreatedBefore: pgtype.Timestamp{
Time: filter.CreatedBefore.Value,
Valid: filter.CreatedBefore.Valid,
},
CreatedAfter: pgtype.Timestamp{
Time: filter.CreatedAfter.Value,
Valid: filter.CreatedAfter.Valid,
},
})
if err != nil {
domain.MongoDBLogger.Error("failed to get all bets",
@ -312,8 +324,19 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
return err
}
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{
EventID: eventID,
FilterStatus: pgtype.Int4{
Int32: int32(domain.OUTCOME_STATUS_PENDING),
Valid: is_filtered,
},
FilterStatus2: pgtype.Int4{
Int32: int32(domain.OUTCOME_STATUS_ERROR),
Valid: is_filtered,
},
})
if err != nil {
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
zap.Int64("event_id", eventID),
@ -364,6 +387,24 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom
return res, nil
}
func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
update, err := s.queries.UpdateBetOutcomeStatusByBetID(ctx, dbgen.UpdateBetOutcomeStatusByBetIDParams{
Status: int32(status),
BetID: id,
})
if err != nil {
domain.MongoDBLogger.Error("failed to update bet outcome status",
zap.Int64("id", id),
zap.Int32("status", int32(status)),
zap.Error(err),
)
return domain.BetOutcome{}, err
}
res := convertDBBetOutcomes(update)
return res, nil
}
func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{
EventID: eventID,
@ -386,10 +427,6 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6
return result, nil
}
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
return s.queries.DeleteBet(ctx, id)
}
// GetBetSummary returns aggregated bet statistics
func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
totalStakes domain.Currency,

View File

@ -32,6 +32,8 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
ManagerName: dbBranch.ManagerName.(string),
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
Balance: domain.Currency(dbBranch.Balance.Int64),
IsActive: dbBranch.IsActive,
WalletIsActive: dbBranch.WalletIsActive.Bool,
}
}
@ -140,6 +142,22 @@ func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter)
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
BranchManagerID: pgtype.Int8{
Int64: filter.BranchManagerID.Value,
Valid: filter.BranchManagerID.Valid,
},
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
CreatedBefore: pgtype.Timestamp{
Time: filter.CreatedBefore.Value,
Valid: filter.CreatedBefore.Valid,
},
CreatedAfter: pgtype.Timestamp{
Time: filter.CreatedAfter.Value,
Valid: filter.CreatedAfter.Valid,
},
})
if err != nil {
return nil, err

View File

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

View File

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

View File

@ -2,12 +2,74 @@ package repository
import (
"context"
"fmt"
"strconv"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"go.uber.org/zap"
)
type DBSettingList struct {
MaxNumberOfOutcomes domain.ValidInt64
BetAmountLimit domain.ValidInt64
DailyTicketPerIP domain.ValidInt64
TotalWinningLimit domain.ValidInt64
}
func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
var dbSettingList DBSettingList
var int64SettingsMap = map[string]*domain.ValidInt64{
"max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes,
"bet_amount_limit": &dbSettingList.BetAmountLimit,
"daily_ticket_limit": &dbSettingList.DailyTicketPerIP,
"total_winnings_limit": &dbSettingList.TotalWinningLimit,
}
for _, setting := range settings {
is_setting_unknown := true
for key, dbSetting := range int64SettingsMap {
if setting.Key == key {
value, err := strconv.ParseInt(setting.Value, 10, 64)
if err != nil {
return domain.SettingList{}, err
}
*dbSetting = domain.ValidInt64{
Value: value,
Valid: true,
}
is_setting_unknown = false
}
}
if is_setting_unknown {
domain.MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key))
}
}
for key, dbSetting := range int64SettingsMap {
if !dbSetting.Valid {
fmt.Printf("setting value not found on database: %v \n", key)
domain.MongoDBLogger.Warn("setting value not found on database", zap.String("setting", key))
}
}
return domain.SettingList{
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
BetAmountLimit: domain.Currency(dbSettingList.BetAmountLimit.Value),
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
TotalWinningLimit: domain.Currency(dbSettingList.TotalWinningLimit.Value),
}, nil
}
func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) {
settings, err := s.queries.GetSettings(ctx)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
return GetDBSettingList(settings)
}
func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
settings, err := s.queries.GetSettings(ctx)
@ -27,9 +89,25 @@ func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
return result, nil
}
func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, error) {
dbSetting, err := s.queries.GetSetting(ctx, key)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
result := domain.Setting{
Key: dbSetting.Key,
Value: dbSetting.Value,
UpdatedAt: dbSetting.UpdatedAt.Time,
}
return result, nil
}
func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{
Key: key,
Key: key,
Value: value,
})
@ -40,7 +118,7 @@ func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Sett
}
setting := domain.Setting{
Key: dbSetting.Key,
Key: dbSetting.Key,
Value: dbSetting.Value,
}

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{
ID: customerWallet.ID,
RegularID: customerWallet.RegularID,
@ -55,9 +55,14 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai
StaticID: customerWallet.StaticID,
StaticBalance: domain.Currency(customerWallet.StaticBalance),
CustomerID: customerWallet.CustomerID,
RegularIsActive: customerWallet.RegularIsActive,
StaticIsActive: customerWallet.StaticIsActive,
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time,
CreatedAt: customerWallet.CreatedAt.Time,
FirstName: customerWallet.FirstName,
LastName: customerWallet.LastName,
PhoneNumber: customerWallet.PhoneNumber.String,
}
}
@ -115,6 +120,19 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa
return result, nil
}
func (s *Store) GetAllCustomerWallets(ctx context.Context) ([]domain.GetCustomerWallet, error) {
customerWallets, err := s.queries.GetAllCustomerWallet(ctx)
if err != nil {
return nil, err
}
var result []domain.GetCustomerWallet = make([]domain.GetCustomerWallet, 0, len(customerWallets))
for _, wallet := range customerWallets {
result = append(result, convertDBGetCustomerWallet(wallet))
}
return result, nil
}
func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)

View File

@ -15,14 +15,14 @@ type BetStore interface {
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error)
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error)
DeleteBet(ctx context.Context, id int64) error
GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
totalStakes domain.Currency,

View File

@ -30,10 +30,11 @@ var (
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
ErrEventHasBeenRemoved = errors.New("Event has been removed")
ErrEventHasNotEnded = errors.New("Event has not ended yet")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
ErrBranchIDRequired = errors.New("Branch ID required for this role")
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
ErrEventHasNotEnded = errors.New("Event has not ended yet")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
ErrBranchIDRequired = errors.New("Branch ID required for this role")
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
)
type Service struct {
@ -326,7 +327,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
newBet.IsShopBet = true
case domain.RoleCustomer:
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID)
if err != nil {
s.mongoLogger.Error("failed to get customer wallets",
zap.Int64("user_id", userID),
@ -334,17 +335,52 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
)
return domain.CreateBetRes{}, err
}
if req.Amount < wallets.RegularBalance.Float32() {
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
} else {
combinedBalance := wallets.RegularBalance + wallets.StaticBalance
if req.Amount > combinedBalance.Float32() {
return domain.CreateBetRes{}, ErrTotalBalanceNotEnough
}
// Empty the regular balance
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
wallets.RegularBalance, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
// Empty remaining from static balance
remainingAmount := wallets.RegularBalance - domain.Currency(req.Amount)
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.StaticID,
remainingAmount, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer static wallet",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("static wallet_id", wallets.StaticID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
userWallet := wallets[0]
_, err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID,
domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer",
zap.Int64("wallet_id", userWallet.ID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
@ -838,8 +874,23 @@ func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID in
return outcomes, nil
}
func (s *Service) DeleteBet(ctx context.Context, id int64) error {
return s.betStore.DeleteBet(ctx, id)
func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error {
_, err := s.betStore.UpdateBetOutcomeStatusByBetID(ctx, id, domain.OUTCOME_STATUS_VOID)
if err != nil {
s.mongoLogger.Error("failed to update bet outcome to void", zap.Int64("id", id),
zap.Error(err),
)
return err
}
err = s.betStore.UpdateStatus(ctx, id, domain.OUTCOME_STATUS_VOID)
if err != nil {
s.mongoLogger.Error("failed to update bet to void", zap.Int64("id", id),
zap.Error(err),
)
return err
}
return nil
}
func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ var (
func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, resultRes json.RawMessage, sportID int64) error {
// TODO: Optimize this since there could be many outcomes with the same event_id and market_id that could be updated the same time
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID)
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, true)
if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err)
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
@ -108,7 +108,7 @@ func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, re
}
func (s *Service) RefundAllOutcomes(ctx context.Context, eventID int64) error {
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID)
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, false)
if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err)

View File

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

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

View File

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

View File

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

View File

@ -57,6 +57,9 @@ func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wall
return s.walletStore.GetWalletsByUser(ctx, id)
}
func (s *Service) GetAllCustomerWallet(ctx context.Context) ([]domain.GetCustomerWallet, error) {
return s.walletStore.GetAllCustomerWallets(ctx)
}
func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
return s.walletStore.GetCustomerWallet(ctx, customerID)
}

View File

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

View File

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

View File

@ -209,13 +209,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse
// @Router /sport/bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64)
var isShopBet domain.ValidBool
isShopBetQuery := c.Query("is_shop")
if isShopBetQuery != "" {
if isShopBetQuery != "" && role == domain.RoleSuperAdmin {
isShopBetParse, err := strconv.ParseBool(isShopBetQuery)
if err != nil {
h.mongoLoggerSvc.Error("Failed to parse is_shop_bet",
@ -231,10 +231,47 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
}
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
BranchID: branchID,
CompanyID: companyID,
IsShopBet: isShopBet,
BranchID: branchID,
CompanyID: companyID,
IsShopBet: isShopBet,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
})
if err != nil {
h.mongoLoggerSvc.Error("Failed to get bets",
@ -432,7 +469,7 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
err = h.betSvc.DeleteBet(c.Context(), id)
err = h.betSvc.SetBetToRemoved(c.Context(), id)
if err != nil {
h.mongoLoggerSvc.Error("Failed to delete bet by ID",
zap.Int64("betID", id),

View File

@ -3,6 +3,7 @@ package handlers
import (
"strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
@ -56,6 +57,7 @@ type BranchRes struct {
BranchManagerID int64 `json:"branch_manager_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"`
IsSelfOwned bool `json:"is_self_owned" example:"false"`
IsActive bool `json:"is_active" example:"false"`
}
type BranchDetailRes struct {
@ -69,6 +71,8 @@ type BranchDetailRes struct {
ManagerName string `json:"manager_name" example:"John Smith"`
ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"`
Balance float32 `json:"balance" example:"100.5"`
IsActive bool `json:"is_active" example:"false"`
WalletIsActive bool `json:"is_wallet_active" example:"false"`
}
func convertBranch(branch domain.Branch) BranchRes {
@ -80,6 +84,7 @@ func convertBranch(branch domain.Branch) BranchRes {
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
IsActive: branch.IsActive,
}
}
@ -95,6 +100,8 @@ func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes {
ManagerName: branch.ManagerName,
ManagerPhoneNumber: branch.ManagerPhoneNumber,
Balance: branch.Balance.Float32(),
IsActive: branch.IsActive,
WalletIsActive: branch.WalletIsActive,
}
}
@ -391,13 +398,63 @@ func (h *Handler) GetAllBranches(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid is_active param", err, nil)
}
branchManagerQuery := c.Query("branch_manager_id")
var branchManagerID domain.ValidInt64
if branchManagerQuery != "" {
parseManagerID, err := strconv.ParseInt(branchManagerQuery, 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse branch_manager_id")
}
branchManagerID = domain.ValidInt64{
Value: parseManagerID,
Valid: true,
}
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
branches, err := h.branchSvc.GetAllBranches(c.Context(),
domain.BranchFilter{
CompanyID: companyID,
IsSuspended: domain.ValidBool{
IsActive: domain.ValidBool{
Value: isActive,
Valid: isActiveValid,
},
BranchManagerID: branchManagerID,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
})
if err != nil {
h.logger.Error("Failed to get branches", "error", err)

View File

@ -184,7 +184,34 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
res := convertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil)
}
// GetCompanyForAdmin godoc
// @Summary Gets company by id
// @Description Gets a single company by id
// @Tags company
// @Accept json
// @Produce json
// @Success 200 {object} CompanyRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /admin-company [get]
func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid company ID", nil, nil)
}
company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value)
if err != nil {
h.logger.Error("Failed to get company by ID", "companyID", companyID.Value, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil)
}
res := convertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil)
}
// GetAllCompanies godoc

View File

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

View File

@ -2,6 +2,7 @@ package handlers
import (
"context"
"os"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
@ -20,7 +21,7 @@ import (
// @Router /api/v1/logs [get]
func GetLogsHandler(appCtx context.Context) fiber.Handler {
return func(c *fiber.Ctx) error {
client, err := mongo.Connect(appCtx, options.Client().ApplyURI("mongodb://root:secret@mongo:27017/?authSource=admin"))
client, err := mongo.Connect(appCtx, options.Client().ApplyURI(os.Getenv("MONGODB_URL")))
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error())
}

View File

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

View File

@ -35,7 +35,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
if err != nil {
switch err {
case ticket.ErrEventHasBeenRemoved, ticket.ErrEventHasNotEnded, ticket.ErrRawOddInvalid:
case ticket.ErrEventHasBeenRemoved, ticket.ErrTicketHasExpired,
ticket.ErrRawOddInvalid, ticket.ErrTooManyOutcomesForTicket,
ticket.ErrTicketAmountTooHigh, ticket.ErrTicketLimitForSingleUser,
ticket.ErrTicketWinningTooHigh:
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return fiber.NewError(fiber.StatusInternalServerError, err.Error())

View File

@ -59,9 +59,8 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
}
type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
}
// SendRegisterCode godoc
@ -99,7 +98,7 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
}
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, req.Provider); err != nil {
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, "twilio"); err != nil {
h.logger.Error("Failed to send register code", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code")
}
@ -108,14 +107,13 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
}
type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
}
// RegisterUser godoc
@ -176,11 +174,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error")
}
newWallet, err := h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
UserID: newUser.ID,
IsWithdraw: true,
IsBettable: true,
})
newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID)
if err != nil {
h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet")
@ -195,7 +189,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
// TODO: Remove later
_, err = h.walletSvc.AddToWallet(
c.Context(), newWallet.ID, domain.ToCurrency(100.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
c.Context(), newWallet.RegularID, domain.ToCurrency(100.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
if err != nil {
h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err)
@ -206,9 +200,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
}
type ResetCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
// Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
}
// SendResetCode godoc
@ -246,7 +240,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
}
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, req.Provider); err != nil {
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, "twilio"); err != nil {
h.logger.Error("Failed to send reset code", "error", err)
fmt.Println(err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")

View File

@ -9,9 +9,6 @@ import (
"github.com/gofiber/fiber/v2"
)
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
type WalletRes struct {
ID int64 `json:"id" example:"1"`
Balance float32 `json:"amount" example:"100.0"`
@ -45,9 +42,14 @@ type CustomerWalletRes struct {
StaticID int64 `json:"static_id" example:"1"`
StaticBalance float32 `json:"static_balance" example:"100.0"`
CustomerID int64 `json:"customer_id" example:"1"`
RegularIsActive bool `json:"regular_is_active" example:"true"`
StaticIsActive bool `json:"static_is_active" example:"true"`
RegularUpdatedAt time.Time `json:"regular_updated_at"`
StaticUpdatedAt time.Time `json:"static_updated_at"`
CreatedAt time.Time `json:"created_at"`
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Smith"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
}
func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
@ -58,9 +60,14 @@ func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
StaticID: wallet.StaticID,
StaticBalance: wallet.StaticBalance.Float32(),
CustomerID: wallet.CustomerID,
RegularIsActive: wallet.RegularIsActive,
StaticIsActive: wallet.StaticIsActive,
RegularUpdatedAt: wallet.RegularUpdatedAt,
StaticUpdatedAt: wallet.StaticUpdatedAt,
CreatedAt: wallet.CreatedAt,
FirstName: wallet.FirstName,
LastName: wallet.LastName,
PhoneNumber: wallet.PhoneNumber,
}
}
@ -173,7 +180,38 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
}
return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil)
}
// GetAllCustomerWallets godoc
// @Summary Get all customer wallets
// @Description Retrieve all customer wallets
// @Tags wallet
// @Accept json
// @Produce json
// @Success 200 {array} CustomerWalletRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /customerWallet [get]
func (h *Handler) GetAllCustomerWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllCustomerWallet(c.Context())
if err != nil {
h.logger.Error("Failed to get wallets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil)
}
var res []CustomerWalletRes = make([]CustomerWalletRes, 0, len(wallets))
for _, wallet := range wallets {
res = append(res, ConvertCustomerWallet(wallet))
}
return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil)
}
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
// UpdateWalletActive godoc
@ -255,13 +293,13 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
// h.logger.Info("Fetching customer wallet", "userID", userID)
wallet, err := h.walletSvc.GetWalletsByUser(c.Context(), userID)
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
}
res := convertWallet(wallet[0])
res := ConvertCustomerWallet(wallet)
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
}

View File

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

View File

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