5 min report fix + arifpay integration
This commit is contained in:
commit
a35d4b37d3
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -8,4 +8,4 @@ build
|
|||
logs/
|
||||
app_logs/
|
||||
backup/
|
||||
|
||||
reports/
|
||||
|
|
|
|||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
|
@ -7,5 +7,12 @@
|
|||
],
|
||||
"cSpell.enabledFileTypes": {
|
||||
"sql": false
|
||||
}
|
||||
},
|
||||
"workbench.editor.customLabels.enabled": true,
|
||||
"workbench.editor.customLabels.patterns": {
|
||||
"**/internal/services/**/service.go": "${dirname}.service",
|
||||
"**/internal/services/**/*.go": "${filename}.${dirname}.service",
|
||||
"**/internal/domain/**/*.go": "${filename}.${dirname}",
|
||||
"**/internal/repository/**/*.go": "${filename}.repo",
|
||||
},
|
||||
}
|
||||
13
cmd/main.go
13
cmd/main.go
|
|
@ -42,7 +42,8 @@ import (
|
|||
issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
|
|
@ -104,13 +105,15 @@ func main() {
|
|||
|
||||
// Initialize services
|
||||
settingSvc := settings.NewService(store)
|
||||
messengerSvc := messenger.NewService(settingSvc, cfg)
|
||||
|
||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||
userSvc := user.NewService(store, store, cfg)
|
||||
userSvc := user.NewService(store, store, messengerSvc, cfg)
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(store, cfg, logger)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||
notificationSvc := notificationservice.New(notificationRepo, domain.MongoDBLogger, logger, cfg, messengerSvc, userSvc)
|
||||
|
||||
var notificatioStore notificationservice.NotificationStore
|
||||
// var userStore user.UserStore
|
||||
|
|
@ -121,6 +124,8 @@ func main() {
|
|||
notificatioStore,
|
||||
// userStore,
|
||||
notificationSvc,
|
||||
userSvc,
|
||||
domain.MongoDBLogger,
|
||||
logger,
|
||||
kafka.NewProducer([]string{"localhost:9092"}, "wallet-events"),
|
||||
)
|
||||
|
|
@ -129,7 +134,7 @@ func main() {
|
|||
companySvc := company.NewService(store)
|
||||
leagueSvc := league.New(store)
|
||||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
|
||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, notificationSvc, logger, domain.MongoDBLogger)
|
||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger)
|
||||
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
|
||||
bonusSvc := bonus.NewService(store)
|
||||
referalRepo := repository.NewReferralRepository(store)
|
||||
|
|
|
|||
|
|
@ -79,4 +79,6 @@ DROP TABLE IF EXISTS events;
|
|||
DROP TABLE IF EXISTS leagues;
|
||||
DROP TABLE IF EXISTS teams;
|
||||
DROP TABLE IF EXISTS settings;
|
||||
DROP TABLE IF EXISTS bonus;
|
||||
DROP TABLE IF EXISTS flags;
|
||||
-- DELETE FROM wallet_transfer;
|
||||
|
|
@ -133,6 +133,7 @@ CREATE TABLE IF NOT EXISTS wallets (
|
|||
is_bettable BOOLEAN NOT NULL,
|
||||
is_transferable BOOLEAN NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
|
|
@ -208,7 +209,7 @@ CREATE TABLE IF NOT EXISTS branches (
|
|||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
profit_percent REAL NOt NULL,
|
||||
profit_percent REAL NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT false,
|
||||
wallet_id BIGINT NOT NULL,
|
||||
branch_manager_id BIGINT NOT NULL,
|
||||
|
|
@ -216,7 +217,11 @@ CREATE TABLE IF NOT EXISTS branches (
|
|||
is_self_owned BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(wallet_id)
|
||||
UNIQUE(wallet_id),
|
||||
CONSTRAINT profit_percentage_check CHECK (
|
||||
profit_percent >= 0
|
||||
AND profit_percent < 1
|
||||
)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS branch_operations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
|
@ -258,7 +263,8 @@ CREATE TABLE events (
|
|||
status TEXT,
|
||||
fetched_at TIMESTAMP DEFAULT now(),
|
||||
source TEXT DEFAULT 'b365api',
|
||||
flagged BOOLEAN NOT NULL DEFAULT false
|
||||
is_featured BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
CREATE TABLE odds (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
|
@ -289,7 +295,11 @@ CREATE TABLE companies (
|
|||
deducted_percentage REAL NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT deducted_percentage_check CHECK (
|
||||
deducted_percentage >= 0
|
||||
AND deducted_percentage < 1
|
||||
)
|
||||
);
|
||||
CREATE TABLE leagues (
|
||||
id BIGINT PRIMARY KEY,
|
||||
|
|
@ -319,6 +329,25 @@ CREATE TABLE bonus (
|
|||
multiplier REAL NOT NULL,
|
||||
balance_cap BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE flags (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE,
|
||||
odd_id BIGINT REFERENCES odds(id),
|
||||
reason TEXT,
|
||||
flagged_at TIMESTAMP DEFAULT NOW(),
|
||||
resolved BOOLEAN DEFAULT FALSE,
|
||||
-- either bet or odd is flagged (not at the same time)
|
||||
CHECK (
|
||||
(
|
||||
bet_id IS NOT NULL
|
||||
AND odd_id IS NULL
|
||||
)
|
||||
OR (
|
||||
bet_id IS NULL
|
||||
AND odd_id IS NOT NULL
|
||||
)
|
||||
)
|
||||
);
|
||||
-- Views
|
||||
CREATE VIEW companies_details AS
|
||||
SELECT companies.*,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
-- Settings Initial Data
|
||||
INSERT INTO settings (key, value)
|
||||
<<<<<<< HEAD
|
||||
VALUES
|
||||
('max_number_of_outcomes', '30'),
|
||||
=======
|
||||
VALUES ('sms_provider', '30'),
|
||||
('max_number_of_outcomes', '30'),
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
('bet_amount_limit', '100000'),
|
||||
('daily_ticket_limit', '50'),
|
||||
('total_winnings_limit', '1000000'),
|
||||
('amount_for_bet_referral', '1000000'),
|
||||
<<<<<<< HEAD
|
||||
('cashback_amount_cap', '1000')
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value;
|
||||
=======
|
||||
('cashback_amount_cap', '1000') ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value;
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ VALUES ('addis_ababa', 'Addis Ababa'),
|
|||
('meki', 'Meki'),
|
||||
('negele_borana', 'Negele Borana'),
|
||||
('alaba_kulito', 'Alaba Kulito'),
|
||||
('alamata 14,', 'Alamata 14,'),
|
||||
('030', '030'),
|
||||
('alamata,', 'Alamata,'),
|
||||
('chiro', 'Chiro'),
|
||||
('tepi', 'Tepi'),
|
||||
('durame', 'Durame'),
|
||||
|
|
|
|||
|
|
@ -101,11 +101,19 @@ WHERE (event_id = $1)
|
|||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE bet_id = $1;
|
||||
-- name: GetBetCount :one
|
||||
-- name: GetBetOutcomeCountByOddID :one
|
||||
SELECT COUNT(*)
|
||||
FROM bet_outcomes
|
||||
WHERE odd_id = $1;
|
||||
-- name: GetBetCountByUserID :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
WHERE user_id = $1
|
||||
AND outcomes_hash = $2;
|
||||
-- name: GetBetCountByOutcomesHash :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
WHERE outcomes_hash = $1;
|
||||
-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ INSERT INTO branches (
|
|||
wallet_id,
|
||||
branch_manager_id,
|
||||
company_id,
|
||||
is_self_owned
|
||||
is_self_owned,
|
||||
profit_percent
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *;
|
||||
-- name: CreateSupportedOperation :one
|
||||
INSERT INTO supported_operations (name, description)
|
||||
|
|
@ -88,6 +89,7 @@ SET name = COALESCE(sqlc.narg(name), name),
|
|||
company_id = COALESCE(sqlc.narg(company_id), company_id),
|
||||
is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned),
|
||||
is_active = COALESCE(sqlc.narg(is_active), is_active),
|
||||
profit_percent = COALESCE(sqlc.narg(profit_percent), profit_percent),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@ WHERE is_live = false
|
|||
events.sport_id = sqlc.narg('sport_id')
|
||||
OR sqlc.narg('sport_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
match_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR sqlc.narg('query') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < sqlc.narg('last_start_time')
|
||||
OR sqlc.narg('last_start_time') IS NULL
|
||||
|
|
@ -170,8 +175,8 @@ WHERE is_live = false
|
|||
OR sqlc.narg('country_code') IS NULL
|
||||
)
|
||||
AND (
|
||||
flagged = sqlc.narg('flagged')
|
||||
OR sqlc.narg('flagged') IS NULL
|
||||
events.is_featured = sqlc.narg('is_featured')
|
||||
OR sqlc.narg('is_featured') IS NULL
|
||||
);
|
||||
-- name: GetPaginatedUpcomingEvents :many
|
||||
SELECT events.*,
|
||||
|
|
@ -189,6 +194,11 @@ WHERE start_time > now()
|
|||
events.sport_id = sqlc.narg('sport_id')
|
||||
OR sqlc.narg('sport_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
match_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR sqlc.narg('query') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < sqlc.narg('last_start_time')
|
||||
OR sqlc.narg('last_start_time') IS NULL
|
||||
|
|
@ -202,8 +212,8 @@ WHERE start_time > now()
|
|||
OR sqlc.narg('country_code') IS NULL
|
||||
)
|
||||
AND (
|
||||
flagged = sqlc.narg('flagged')
|
||||
OR sqlc.narg('flagged') IS NULL
|
||||
events.is_featured = sqlc.narg('is_featured')
|
||||
OR sqlc.narg('is_featured') IS NULL
|
||||
)
|
||||
ORDER BY start_time ASC
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
|
|
@ -219,9 +229,9 @@ UPDATE events
|
|||
SET score = $1,
|
||||
status = $2
|
||||
WHERE id = $3;
|
||||
-- name: UpdateFlagged :exec
|
||||
-- name: UpdateFeatured :exec
|
||||
UPDATE events
|
||||
SET flagged = $1
|
||||
SET is_featured = $1
|
||||
WHERE id = $2;
|
||||
-- name: DeleteEvent :exec
|
||||
DELETE FROM events
|
||||
|
|
|
|||
8
db/query/flags.sql
Normal file
8
db/query/flags.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- name: CreateFlag :one
|
||||
INSERT INTO flags (
|
||||
bet_id,
|
||||
odd_id,
|
||||
reason
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
) RETURNING *;
|
||||
|
|
@ -41,6 +41,8 @@ WHERE (
|
|||
is_featured = sqlc.narg('is_featured')
|
||||
OR sqlc.narg('is_featured') IS NULL
|
||||
)
|
||||
ORDER BY is_featured DESC,
|
||||
name ASC
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
-- name: GetFeaturedLeagues :many
|
||||
SELECT id,
|
||||
|
|
|
|||
7
db/query/location.sql
Normal file
7
db/query/location.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- name: GetAllBranchLocations :many
|
||||
SELECT *
|
||||
FROM branch_locations
|
||||
WHERE (
|
||||
value ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR sqlc.narg('query') IS NULL
|
||||
);
|
||||
|
|
@ -11,25 +11,3 @@ VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
|
|||
UPDATE
|
||||
SET value = EXCLUDED.value
|
||||
RETURNING *;
|
||||
|
||||
-- name: SetInitialData :exec
|
||||
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;
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('amount_for_bet_referral', '1000000') ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value;
|
||||
|
|
@ -193,3 +193,8 @@ WHERE (
|
|||
email = $2
|
||||
OR phone_number = $3
|
||||
);
|
||||
-- name: GetAdminByCompanyID :one
|
||||
SELECT users.*
|
||||
FROM companies
|
||||
JOIN users ON companies.admin_id = users.id
|
||||
where companies.id = $1;
|
||||
|
|
@ -3,9 +3,10 @@ INSERT INTO wallets (
|
|||
is_withdraw,
|
||||
is_bettable,
|
||||
is_transferable,
|
||||
user_id
|
||||
user_id,
|
||||
type
|
||||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *;
|
||||
-- name: CreateCustomerWallet :one
|
||||
INSERT INTO customer_wallets (
|
||||
|
|
|
|||
|
|
@ -282,20 +282,33 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetBetCount = `-- name: GetBetCount :one
|
||||
const GetBetCountByOutcomesHash = `-- name: GetBetCountByOutcomesHash :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
WHERE outcomes_hash = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetBetCountByOutcomesHash, outcomesHash)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const GetBetCountByUserID = `-- name: GetBetCountByUserID :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
WHERE user_id = $1
|
||||
AND outcomes_hash = $2
|
||||
`
|
||||
|
||||
type GetBetCountParams struct {
|
||||
type GetBetCountByUserIDParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBetCount(ctx context.Context, arg GetBetCountParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetBetCount, arg.UserID, arg.OutcomesHash)
|
||||
func (q *Queries) GetBetCountByUserID(ctx context.Context, arg GetBetCountByUserIDParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetBetCountByUserID, arg.UserID, arg.OutcomesHash)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
|
|
@ -397,6 +410,19 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeB
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetBetOutcomeCountByOddID = `-- name: GetBetOutcomeCountByOddID :one
|
||||
SELECT COUNT(*)
|
||||
FROM bet_outcomes
|
||||
WHERE odd_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetBetOutcomeCountByOddID, oddID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const GetBetsForCashback = `-- name: GetBetsForCashback :many
|
||||
SELECT id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes
|
||||
FROM bet_with_outcomes
|
||||
|
|
|
|||
|
|
@ -18,19 +18,21 @@ INSERT INTO branches (
|
|||
wallet_id,
|
||||
branch_manager_id,
|
||||
company_id,
|
||||
is_self_owned
|
||||
is_self_owned,
|
||||
profit_percent
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateBranchParams struct {
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
BranchManagerID int64 `json:"branch_manager_id"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
IsSelfOwned bool `json:"is_self_owned"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
BranchManagerID int64 `json:"branch_manager_id"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
IsSelfOwned bool `json:"is_self_owned"`
|
||||
ProfitPercent float32 `json:"profit_percent"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Branch, error) {
|
||||
|
|
@ -41,6 +43,7 @@ func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Bra
|
|||
arg.BranchManagerID,
|
||||
arg.CompanyID,
|
||||
arg.IsSelfOwned,
|
||||
arg.ProfitPercent,
|
||||
)
|
||||
var i Branch
|
||||
err := row.Scan(
|
||||
|
|
@ -498,19 +501,21 @@ SET name = COALESCE($2, name),
|
|||
company_id = COALESCE($5, company_id),
|
||||
is_self_owned = COALESCE($6, is_self_owned),
|
||||
is_active = COALESCE($7, is_active),
|
||||
profit_percent = COALESCE($8, profit_percent),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateBranchParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Location pgtype.Text `json:"location"`
|
||||
BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
IsSelfOwned pgtype.Bool `json:"is_self_owned"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Location pgtype.Text `json:"location"`
|
||||
BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
IsSelfOwned pgtype.Bool `json:"is_self_owned"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
ProfitPercent pgtype.Float4 `json:"profit_percent"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) {
|
||||
|
|
@ -522,6 +527,7 @@ func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Bra
|
|||
arg.CompanyID,
|
||||
arg.IsSelfOwned,
|
||||
arg.IsActive,
|
||||
arg.ProfitPercent,
|
||||
)
|
||||
var i Branch
|
||||
err := row.Scan(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func (q *Queries) DeleteEvent(ctx context.Context, id string) error {
|
|||
}
|
||||
|
||||
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
|
||||
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged
|
||||
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, is_featured, is_active
|
||||
FROM events
|
||||
WHERE start_time > now()
|
||||
AND is_live = false
|
||||
|
|
@ -62,7 +62,8 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
|
|||
&i.Status,
|
||||
&i.FetchedAt,
|
||||
&i.Source,
|
||||
&i.Flagged,
|
||||
&i.IsFeatured,
|
||||
&i.IsActive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -75,7 +76,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
|
|||
}
|
||||
|
||||
const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many
|
||||
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged,
|
||||
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.is_featured, events.is_active,
|
||||
leagues.country_code as league_cc
|
||||
FROM events
|
||||
LEFT JOIN leagues ON leagues.id = league_id
|
||||
|
|
@ -110,7 +111,8 @@ type GetExpiredUpcomingEventsRow struct {
|
|||
Status pgtype.Text `json:"status"`
|
||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
||||
Source pgtype.Text `json:"source"`
|
||||
Flagged bool `json:"flagged"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsActive bool `json:"is_active"`
|
||||
LeagueCc_2 pgtype.Text `json:"league_cc_2"`
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +148,8 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
|
|||
&i.Status,
|
||||
&i.FetchedAt,
|
||||
&i.Source,
|
||||
&i.Flagged,
|
||||
&i.IsFeatured,
|
||||
&i.IsActive,
|
||||
&i.LeagueCc_2,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -160,7 +163,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
|
|||
}
|
||||
|
||||
const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many
|
||||
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged,
|
||||
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.is_featured, events.is_active,
|
||||
leagues.country_code as league_cc
|
||||
FROM events
|
||||
LEFT JOIN leagues ON leagues.id = league_id
|
||||
|
|
@ -176,32 +179,38 @@ WHERE start_time > now()
|
|||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < $3
|
||||
match_name ILIKE '%' || $3 || '%'
|
||||
OR league_name ILIKE '%' || $3 || '%'
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time > $4
|
||||
start_time < $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
AND (
|
||||
leagues.country_code = $5
|
||||
start_time > $5
|
||||
OR $5 IS NULL
|
||||
)
|
||||
AND (
|
||||
flagged = $6
|
||||
leagues.country_code = $6
|
||||
OR $6 IS NULL
|
||||
)
|
||||
AND (
|
||||
events.is_featured = $7
|
||||
OR $7 IS NULL
|
||||
)
|
||||
ORDER BY start_time ASC
|
||||
LIMIT $8 OFFSET $7
|
||||
LIMIT $9 OFFSET $8
|
||||
`
|
||||
|
||||
type GetPaginatedUpcomingEventsParams struct {
|
||||
LeagueID pgtype.Int4 `json:"league_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
Query pgtype.Text `json:"query"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
CountryCode pgtype.Text `json:"country_code"`
|
||||
Flagged pgtype.Bool `json:"flagged"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
|
@ -229,7 +238,8 @@ type GetPaginatedUpcomingEventsRow struct {
|
|||
Status pgtype.Text `json:"status"`
|
||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
||||
Source pgtype.Text `json:"source"`
|
||||
Flagged bool `json:"flagged"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsActive bool `json:"is_active"`
|
||||
LeagueCc_2 pgtype.Text `json:"league_cc_2"`
|
||||
}
|
||||
|
||||
|
|
@ -237,10 +247,11 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
|
|||
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents,
|
||||
arg.LeagueID,
|
||||
arg.SportID,
|
||||
arg.Query,
|
||||
arg.LastStartTime,
|
||||
arg.FirstStartTime,
|
||||
arg.CountryCode,
|
||||
arg.Flagged,
|
||||
arg.IsFeatured,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
|
|
@ -274,7 +285,8 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
|
|||
&i.Status,
|
||||
&i.FetchedAt,
|
||||
&i.Source,
|
||||
&i.Flagged,
|
||||
&i.IsFeatured,
|
||||
&i.IsActive,
|
||||
&i.LeagueCc_2,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -302,40 +314,47 @@ WHERE is_live = false
|
|||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < $3
|
||||
match_name ILIKE '%' || $3 || '%'
|
||||
OR league_name ILIKE '%' || $3 || '%'
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time > $4
|
||||
start_time < $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
AND (
|
||||
leagues.country_code = $5
|
||||
start_time > $5
|
||||
OR $5 IS NULL
|
||||
)
|
||||
AND (
|
||||
flagged = $6
|
||||
leagues.country_code = $6
|
||||
OR $6 IS NULL
|
||||
)
|
||||
AND (
|
||||
events.is_featured = $7
|
||||
OR $7 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalEventsParams struct {
|
||||
LeagueID pgtype.Int4 `json:"league_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
Query pgtype.Text `json:"query"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
CountryCode pgtype.Text `json:"country_code"`
|
||||
Flagged pgtype.Bool `json:"flagged"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalEvents,
|
||||
arg.LeagueID,
|
||||
arg.SportID,
|
||||
arg.Query,
|
||||
arg.LastStartTime,
|
||||
arg.FirstStartTime,
|
||||
arg.CountryCode,
|
||||
arg.Flagged,
|
||||
arg.IsFeatured,
|
||||
)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
|
|
@ -343,7 +362,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
|
|||
}
|
||||
|
||||
const GetUpcomingByID = `-- name: GetUpcomingByID :one
|
||||
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged
|
||||
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, is_featured, is_active
|
||||
FROM events
|
||||
WHERE id = $1
|
||||
AND is_live = false
|
||||
|
|
@ -377,7 +396,8 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (Event, error)
|
|||
&i.Status,
|
||||
&i.FetchedAt,
|
||||
&i.Source,
|
||||
&i.Flagged,
|
||||
&i.IsFeatured,
|
||||
&i.IsActive,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -623,19 +643,19 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) {
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateFlagged = `-- name: UpdateFlagged :exec
|
||||
const UpdateFeatured = `-- name: UpdateFeatured :exec
|
||||
UPDATE events
|
||||
SET flagged = $1
|
||||
SET is_featured = $1
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateFlaggedParams struct {
|
||||
Flagged bool `json:"flagged"`
|
||||
ID string `json:"id"`
|
||||
type UpdateFeaturedParams struct {
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateFlagged(ctx context.Context, arg UpdateFlaggedParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateFlagged, arg.Flagged, arg.ID)
|
||||
func (q *Queries) UpdateFeatured(ctx context.Context, arg UpdateFeaturedParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateFeatured, arg.IsFeatured, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
42
gen/db/flags.sql.go
Normal file
42
gen/db/flags.sql.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// source: flags.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const CreateFlag = `-- name: CreateFlag :one
|
||||
INSERT INTO flags (
|
||||
bet_id,
|
||||
odd_id,
|
||||
reason
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
) RETURNING id, bet_id, odd_id, reason, flagged_at, resolved
|
||||
`
|
||||
|
||||
type CreateFlagParams struct {
|
||||
BetID pgtype.Int8 `json:"bet_id"`
|
||||
OddID pgtype.Int8 `json:"odd_id"`
|
||||
Reason pgtype.Text `json:"reason"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateFlag(ctx context.Context, arg CreateFlagParams) (Flag, error) {
|
||||
row := q.db.QueryRow(ctx, CreateFlag, arg.BetID, arg.OddID, arg.Reason)
|
||||
var i Flag
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.BetID,
|
||||
&i.OddID,
|
||||
&i.Reason,
|
||||
&i.FlaggedAt,
|
||||
&i.Resolved,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -52,6 +52,8 @@ WHERE (
|
|||
is_featured = $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
ORDER BY is_featured DESC,
|
||||
name ASC
|
||||
LIMIT $6 OFFSET $5
|
||||
`
|
||||
|
||||
|
|
|
|||
41
gen/db/location.sql.go
Normal file
41
gen/db/location.sql.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// source: location.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const GetAllBranchLocations = `-- name: GetAllBranchLocations :many
|
||||
SELECT key, value
|
||||
FROM branch_locations
|
||||
WHERE (
|
||||
value ILIKE '%' || $1 || '%'
|
||||
OR $1 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllBranchLocations(ctx context.Context, query pgtype.Text) ([]BranchLocation, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllBranchLocations, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BranchLocation
|
||||
for rows.Next() {
|
||||
var i BranchLocation
|
||||
if err := rows.Scan(&i.Key, &i.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
|
@ -257,7 +257,8 @@ type Event struct {
|
|||
Status pgtype.Text `json:"status"`
|
||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
||||
Source pgtype.Text `json:"source"`
|
||||
Flagged bool `json:"flagged"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type ExchangeRate struct {
|
||||
|
|
@ -276,6 +277,15 @@ type FavoriteGame struct {
|
|||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
type Flag struct {
|
||||
ID int64 `json:"id"`
|
||||
BetID pgtype.Int8 `json:"bet_id"`
|
||||
OddID pgtype.Int8 `json:"odd_id"`
|
||||
Reason pgtype.Text `json:"reason"`
|
||||
FlaggedAt pgtype.Timestamp `json:"flagged_at"`
|
||||
Resolved pgtype.Bool `json:"resolved"`
|
||||
}
|
||||
|
||||
type League struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -664,6 +674,7 @@ type Wallet struct {
|
|||
IsBettable bool `json:"is_bettable"`
|
||||
IsTransferable bool `json:"is_transferable"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
|
|
|
|||
|
|
@ -81,15 +81,3 @@ func (q *Queries) SaveSetting(ctx context.Context, arg SaveSettingParams) (Setti
|
|||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const SetInitialData = `-- name: SetInitialData :exec
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value
|
||||
`
|
||||
|
||||
func (q *Queries) SetInitialData(ctx context.Context) error {
|
||||
_, err := q.db.Exec(ctx, SetInitialData)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,37 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
const GetAdminByCompanyID = `-- name: GetAdminByCompanyID :one
|
||||
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by
|
||||
FROM companies
|
||||
JOIN users ON companies.admin_id = users.id
|
||||
where companies.id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetAdminByCompanyID(ctx context.Context, id int64) (User, error) {
|
||||
row := q.db.QueryRow(ctx, GetAdminByCompanyID, id)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
&i.Password,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetAllUsers = `-- name: GetAllUsers :many
|
||||
SELECT id,
|
||||
first_name,
|
||||
|
|
|
|||
|
|
@ -46,17 +46,19 @@ INSERT INTO wallets (
|
|||
is_withdraw,
|
||||
is_bettable,
|
||||
is_transferable,
|
||||
user_id
|
||||
user_id,
|
||||
type
|
||||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||
`
|
||||
|
||||
type CreateWalletParams struct {
|
||||
IsWithdraw bool `json:"is_withdraw"`
|
||||
IsBettable bool `json:"is_bettable"`
|
||||
IsTransferable bool `json:"is_transferable"`
|
||||
UserID int64 `json:"user_id"`
|
||||
IsWithdraw bool `json:"is_withdraw"`
|
||||
IsBettable bool `json:"is_bettable"`
|
||||
IsTransferable bool `json:"is_transferable"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wallet, error) {
|
||||
|
|
@ -65,6 +67,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
|
|||
arg.IsBettable,
|
||||
arg.IsTransferable,
|
||||
arg.UserID,
|
||||
arg.Type,
|
||||
)
|
||||
var i Wallet
|
||||
err := row.Scan(
|
||||
|
|
@ -74,6 +77,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
|
|||
&i.IsBettable,
|
||||
&i.IsTransferable,
|
||||
&i.UserID,
|
||||
&i.Type,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -184,7 +188,7 @@ func (q *Queries) GetAllCustomerWallet(ctx context.Context) ([]CustomerWalletDet
|
|||
}
|
||||
|
||||
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, type, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||
FROM wallets
|
||||
`
|
||||
|
||||
|
|
@ -204,6 +208,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
|||
&i.IsBettable,
|
||||
&i.IsTransferable,
|
||||
&i.UserID,
|
||||
&i.Type,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -314,7 +319,7 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (Cust
|
|||
}
|
||||
|
||||
const GetWalletByID = `-- name: GetWalletByID :one
|
||||
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, type, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||
FROM wallets
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -329,6 +334,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
|||
&i.IsBettable,
|
||||
&i.IsTransferable,
|
||||
&i.UserID,
|
||||
&i.Type,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -340,7 +346,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
|||
}
|
||||
|
||||
const GetWalletByUserID = `-- name: GetWalletByUserID :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, type, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||
FROM wallets
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
|
@ -361,6 +367,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet
|
|||
&i.IsBettable,
|
||||
&i.IsTransferable,
|
||||
&i.UserID,
|
||||
&i.Type,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,15 @@ type BetFilter struct {
|
|||
CreatedAfter ValidTime
|
||||
}
|
||||
|
||||
type Flag struct {
|
||||
ID int64
|
||||
BetID int64
|
||||
OddID int64
|
||||
Reason string
|
||||
FlaggedAt time.Time
|
||||
Resolved bool
|
||||
}
|
||||
|
||||
type GetBet struct {
|
||||
ID int64
|
||||
Amount Currency
|
||||
|
|
@ -93,17 +102,23 @@ type CreateBetOutcomeReq struct {
|
|||
}
|
||||
|
||||
type CreateBetReq struct {
|
||||
Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"`
|
||||
Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"`
|
||||
BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"`
|
||||
Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"`
|
||||
Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"`
|
||||
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
type CreateBetWithFastCodeReq struct {
|
||||
type CreateBetWithFastCodeReq struct {
|
||||
FastCode string `json:"fast_code"`
|
||||
Amount float32 `json:"amount"`
|
||||
BranchID *int64 `json:"branch_id"`
|
||||
}
|
||||
|
||||
type CreateFlagReq struct {
|
||||
BetID int64
|
||||
OddID int64
|
||||
Reason string
|
||||
}
|
||||
|
||||
type RandomBetReq struct {
|
||||
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
|
||||
NumberOfBets int64 `json:"number_of_bets" validate:"required" example:"1"`
|
||||
|
|
@ -117,6 +132,7 @@ type CreateBetRes struct {
|
|||
UserID int64 `json:"user_id" example:"2"`
|
||||
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
||||
CreatedNumber int64 `json:"created_number" example:"2"`
|
||||
FastCode string `json:"fast_code"`
|
||||
}
|
||||
type BetRes struct {
|
||||
ID int64 `json:"id" example:"1"`
|
||||
|
|
@ -140,6 +156,8 @@ func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
|
|||
Status: bet.Status,
|
||||
UserID: bet.UserID,
|
||||
CreatedNumber: createdNumber,
|
||||
IsShopBet: bet.IsShopBet,
|
||||
FastCode: bet.FastCode,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Branch struct {
|
||||
ID int64
|
||||
Name string
|
||||
Location string
|
||||
WalletID int64
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsActive bool
|
||||
IsSelfOwned bool
|
||||
ID int64
|
||||
Name string
|
||||
Location string
|
||||
WalletID int64
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsActive bool
|
||||
IsSelfOwned bool
|
||||
ProfitPercentage float32
|
||||
}
|
||||
|
||||
type BranchLocation struct {
|
||||
Key string `json:"key" example:"addis_ababa" `
|
||||
Name string `json:"name" example:"Addis Ababa"`
|
||||
}
|
||||
|
||||
type BranchFilter struct {
|
||||
|
|
@ -33,6 +44,7 @@ type BranchDetail struct {
|
|||
ManagerName string
|
||||
ManagerPhoneNumber string
|
||||
WalletIsActive bool
|
||||
ProfitPercentage float32
|
||||
}
|
||||
|
||||
type SupportedOperation struct {
|
||||
|
|
@ -48,22 +60,24 @@ type BranchOperation struct {
|
|||
}
|
||||
|
||||
type CreateBranch struct {
|
||||
Name string
|
||||
Location string
|
||||
WalletID int64
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsSelfOwned bool
|
||||
Name string
|
||||
Location string
|
||||
WalletID int64
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsSelfOwned bool
|
||||
ProfitPercentage float32
|
||||
}
|
||||
|
||||
type UpdateBranch struct {
|
||||
ID int64
|
||||
Name *string
|
||||
Location *string
|
||||
BranchManagerID *int64
|
||||
CompanyID *int64
|
||||
IsSelfOwned *bool
|
||||
IsActive *bool
|
||||
ID int64
|
||||
Name *string
|
||||
Location *string
|
||||
BranchManagerID *int64
|
||||
CompanyID *int64
|
||||
IsSelfOwned *bool
|
||||
IsActive *bool
|
||||
ProfitPercentage *float32
|
||||
}
|
||||
|
||||
type CreateSupportedOperation struct {
|
||||
|
|
@ -76,21 +90,23 @@ type CreateBranchOperation struct {
|
|||
}
|
||||
|
||||
type CreateBranchReq struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=100" example:"4-kilo Branch"`
|
||||
Location string `json:"location" validate:"required,min=3,max=100" example:"Addis Ababa"`
|
||||
BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
|
||||
Operations []int64 `json:"operations" validate:"required,dive,gt=0"`
|
||||
Name string `json:"name" validate:"required,min=3,max=100" example:"4-kilo Branch"`
|
||||
Location string `json:"location" validate:"required,min=3,max=100" example:"Addis Ababa"`
|
||||
BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"`
|
||||
ProfitPercentage float32 `json:"profit_percentage" example:"0.1" validate:"lt=1" `
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
|
||||
Operations []int64 `json:"operations" validate:"required,dive,gt=0"`
|
||||
}
|
||||
|
||||
type UpdateBranchReq struct {
|
||||
Name *string `json:"name,omitempty" example:"4-kilo Branch"`
|
||||
Location *string `json:"location,omitempty" example:"Addis Ababa"`
|
||||
BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
|
||||
IsActive *bool `json:"is_active,omitempty" example:"false"`
|
||||
Name *string `json:"name,omitempty" example:"4-kilo Branch"`
|
||||
Location *string `json:"location,omitempty" example:"Addis Ababa"`
|
||||
BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
|
||||
IsActive *bool `json:"is_active,omitempty" example:"false"`
|
||||
ProfitPercentage *float32 `json:"profit_percentage,omitempty" example:"0.1" validate:"lt=1" `
|
||||
}
|
||||
|
||||
type CreateSupportedOperationReq struct {
|
||||
|
|
@ -115,14 +131,15 @@ type BranchOperationRes struct {
|
|||
}
|
||||
|
||||
type BranchRes struct {
|
||||
ID int64 `json:"id" example:"1"`
|
||||
Name string `json:"name" example:"4-kilo Branch"`
|
||||
Location string `json:"location" example:"Addis Ababa"`
|
||||
WalletID int64 `json:"wallet_id" example:"1"`
|
||||
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"`
|
||||
ID int64 `json:"id" example:"1"`
|
||||
Name string `json:"name" example:"4-kilo Branch"`
|
||||
Location string `json:"location" example:"Addis Ababa"`
|
||||
WalletID int64 `json:"wallet_id" example:"1"`
|
||||
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"`
|
||||
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
|
||||
}
|
||||
|
||||
type BranchDetailRes struct {
|
||||
|
|
@ -138,18 +155,20 @@ type BranchDetailRes struct {
|
|||
Balance float32 `json:"balance" example:"100.5"`
|
||||
IsActive bool `json:"is_active" example:"false"`
|
||||
WalletIsActive bool `json:"is_wallet_active" example:"false"`
|
||||
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
|
||||
}
|
||||
|
||||
func ConvertBranch(branch Branch) BranchRes {
|
||||
return BranchRes{
|
||||
ID: branch.ID,
|
||||
Name: branch.Name,
|
||||
Location: branch.Location,
|
||||
WalletID: branch.WalletID,
|
||||
BranchManagerID: branch.BranchManagerID,
|
||||
CompanyID: branch.CompanyID,
|
||||
IsSelfOwned: branch.IsSelfOwned,
|
||||
IsActive: branch.IsActive,
|
||||
ID: branch.ID,
|
||||
Name: branch.Name,
|
||||
Location: branch.Location,
|
||||
WalletID: branch.WalletID,
|
||||
BranchManagerID: branch.BranchManagerID,
|
||||
CompanyID: branch.CompanyID,
|
||||
IsSelfOwned: branch.IsSelfOwned,
|
||||
IsActive: branch.IsActive,
|
||||
ProfitPercentage: branch.ProfitPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,5 +186,103 @@ func ConvertBranchDetail(branch BranchDetail) BranchDetailRes {
|
|||
Balance: branch.Balance.Float32(),
|
||||
IsActive: branch.IsActive,
|
||||
WalletIsActive: branch.WalletIsActive,
|
||||
ProfitPercentage: branch.ProfitPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateBranch(branch CreateBranch) dbgen.CreateBranchParams {
|
||||
return dbgen.CreateBranchParams{
|
||||
Name: branch.Name,
|
||||
Location: branch.Location,
|
||||
WalletID: branch.WalletID,
|
||||
BranchManagerID: branch.BranchManagerID,
|
||||
CompanyID: branch.CompanyID,
|
||||
IsSelfOwned: branch.IsSelfOwned,
|
||||
ProfitPercent: branch.ProfitPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBBranchDetail(dbBranch dbgen.BranchDetail) BranchDetail {
|
||||
return BranchDetail{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
ManagerName: dbBranch.ManagerName.(string),
|
||||
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
|
||||
Balance: Currency(dbBranch.Balance.Int64),
|
||||
IsActive: dbBranch.IsActive,
|
||||
WalletIsActive: dbBranch.WalletIsActive.Bool,
|
||||
ProfitPercentage: dbBranch.ProfitPercent,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBBranch(dbBranch dbgen.Branch) Branch {
|
||||
return Branch{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
IsActive: dbBranch.IsActive,
|
||||
ProfitPercentage: dbBranch.ProfitPercent,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUpdateBranch(updateBranch UpdateBranch) dbgen.UpdateBranchParams {
|
||||
|
||||
var newUpdateBranch dbgen.UpdateBranchParams
|
||||
|
||||
newUpdateBranch.ID = updateBranch.ID
|
||||
|
||||
if updateBranch.Name != nil {
|
||||
newUpdateBranch.Name = pgtype.Text{
|
||||
String: *updateBranch.Name,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.Location != nil {
|
||||
newUpdateBranch.Location = pgtype.Text{
|
||||
String: *updateBranch.Location,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.BranchManagerID != nil {
|
||||
newUpdateBranch.BranchManagerID = pgtype.Int8{
|
||||
Int64: *updateBranch.BranchManagerID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.CompanyID != nil {
|
||||
newUpdateBranch.CompanyID = pgtype.Int8{
|
||||
Int64: *updateBranch.CompanyID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.IsSelfOwned != nil {
|
||||
newUpdateBranch.IsSelfOwned = pgtype.Bool{
|
||||
Bool: *updateBranch.IsSelfOwned,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.IsActive != nil {
|
||||
newUpdateBranch.IsActive = pgtype.Bool{
|
||||
Bool: *updateBranch.IsActive,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
if updateBranch.ProfitPercentage != nil {
|
||||
newUpdateBranch.ProfitPercent = pgtype.Float4{
|
||||
Float32: *updateBranch.ProfitPercentage,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
return newUpdateBranch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ type UpdateCompany struct {
|
|||
}
|
||||
|
||||
type CreateCompanyReq struct {
|
||||
Name string `json:"name" example:"CompanyName"`
|
||||
AdminID int64 `json:"admin_id" example:"1"`
|
||||
Name string `json:"name" example:"CompanyName"`
|
||||
AdminID int64 `json:"admin_id" example:"1"`
|
||||
DeductedPercentage float32 `json:"deducted_percentage" example:"0.1" validate:"lt=1"`
|
||||
}
|
||||
type UpdateCompanyReq struct {
|
||||
Name *string `json:"name,omitempty" example:"CompanyName"`
|
||||
|
|
@ -111,6 +112,7 @@ func ConvertGetCompany(company GetCompany) GetCompanyRes {
|
|||
AdminFirstName: company.AdminFirstName,
|
||||
AdminLastName: company.AdminLastName,
|
||||
AdminPhoneNumber: company.AdminPhoneNumber,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,8 @@ type UpcomingEvent struct {
|
|||
StartTime time.Time `json:"start_time"` // Converted from "time" field in UNIX format
|
||||
Source string `json:"source"` // bet api provider (bet365, betfair)
|
||||
Status EventStatus `json:"status"` //Match Status for event
|
||||
Flagged bool `json:"flagged"` //Whether the event is flagged or not
|
||||
IsFeatured bool `json:"is_featured"` //Whether the event is featured or not
|
||||
IsActive bool `json:"is_active"` //Whether the event is featured or not
|
||||
}
|
||||
type MatchResult struct {
|
||||
EventID string
|
||||
|
|
@ -120,6 +121,7 @@ type Odds struct {
|
|||
}
|
||||
|
||||
type EventFilter struct {
|
||||
Query ValidString
|
||||
SportID ValidInt32
|
||||
LeagueID ValidInt32
|
||||
CountryCode ValidString
|
||||
|
|
@ -128,5 +130,5 @@ type EventFilter struct {
|
|||
Limit ValidInt64
|
||||
Offset ValidInt64
|
||||
MatchStatus ValidString // e.g., "upcoming", "in_play", "ended"
|
||||
Flagged ValidBool
|
||||
Featured ValidBool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ var (
|
|||
ISSUE_TYPE_ODDS ReportedIssueType = "odds"
|
||||
ISSUE_TYPE_EVENTS ReportedIssueType = "events"
|
||||
ISSUE_TYPE_BRANCH ReportedIssueType = "branch"
|
||||
ISSUE_TYPE_USER ReportedIssueType = "branch"
|
||||
ISSUE_TYPE_USER ReportedIssueType = "user"
|
||||
ISSUE_TYPE_LOGIN ReportedIssueType = "login"
|
||||
ISSUE_TYPE_REGISTER ReportedIssueType = "register"
|
||||
ISSUE_TYPE_RESET_PASSWORD ReportedIssueType = "reset_password"
|
||||
|
|
|
|||
|
|
@ -26,12 +26,7 @@ const (
|
|||
OtpMediumSms OtpMedium = "sms"
|
||||
)
|
||||
|
||||
type OtpProvider string
|
||||
|
||||
const (
|
||||
TwilioSms OtpProvider = "twilio"
|
||||
AfroMessage OtpProvider = "aformessage"
|
||||
)
|
||||
|
||||
type Otp struct {
|
||||
ID int64
|
||||
|
|
|
|||
|
|
@ -17,15 +17,17 @@ type SettingRes struct {
|
|||
}
|
||||
|
||||
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"`
|
||||
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
|
||||
CashbackAmountCap Currency `json:"cashback_amount_cap"`
|
||||
SMSProvider SMSProvider `json:"sms_provider"`
|
||||
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"`
|
||||
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
|
||||
CashbackAmountCap Currency `json:"cashback_amount_cap"`
|
||||
}
|
||||
|
||||
type DBSettingList struct {
|
||||
SMSProvider ValidString
|
||||
MaxNumberOfOutcomes ValidInt64
|
||||
BetAmountLimit ValidInt64
|
||||
DailyTicketPerIP ValidInt64
|
||||
|
|
@ -45,8 +47,27 @@ func ConvertInt64SettingsMap(dbSettingList *DBSettingList) map[string]*ValidInt6
|
|||
}
|
||||
}
|
||||
|
||||
func ConvertStringSettingsMap(dbSettingList *DBSettingList) map[string]*ValidString {
|
||||
return map[string]*ValidString{
|
||||
"sms_provider": &dbSettingList.SMSProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertBoolSettingsMap(dbSettingList *DBSettingList) map[string]*ValidBool {
|
||||
return map[string]*ValidBool{}
|
||||
}
|
||||
|
||||
func ConvertFloat32SettingsMap(dbSettingList *DBSettingList) map[string]*ValidFloat32 {
|
||||
return map[string]*ValidFloat32{}
|
||||
}
|
||||
|
||||
func ConvertTimeSettingsMap(dbSettingList *DBSettingList) map[string]*ValidTime {
|
||||
return map[string]*ValidTime{}
|
||||
}
|
||||
|
||||
func ConvertDBSetting(dbSettingList DBSettingList) SettingList {
|
||||
return SettingList{
|
||||
SMSProvider: SMSProvider(dbSettingList.SMSProvider.Value),
|
||||
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
|
||||
BetAmountLimit: Currency(dbSettingList.BetAmountLimit.Value),
|
||||
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
|
||||
|
|
|
|||
18
internal/domain/sms.go
Normal file
18
internal/domain/sms.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package domain
|
||||
|
||||
type SMSProvider string
|
||||
|
||||
const (
|
||||
TwilioSms SMSProvider = "twilio"
|
||||
AfroMessage SMSProvider = "afro_message"
|
||||
)
|
||||
|
||||
// IsValid checks if the SMSProvider is a valid enum value
|
||||
func (s SMSProvider) IsValid() bool {
|
||||
switch s {
|
||||
case TwilioSms, AfroMessage:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ type Wallet struct {
|
|||
IsTransferable bool
|
||||
IsActive bool
|
||||
UserID int64
|
||||
Type WalletType
|
||||
UpdatedAt time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
|
@ -63,6 +64,7 @@ type CreateWallet struct {
|
|||
IsBettable bool
|
||||
IsTransferable bool
|
||||
UserID int64
|
||||
Type WalletType
|
||||
}
|
||||
|
||||
type CreateCustomerWallet struct {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,17 @@ func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
|
|||
}
|
||||
}
|
||||
|
||||
func convertDBFlag(flag dbgen.Flag) domain.Flag {
|
||||
return domain.Flag{
|
||||
ID: flag.ID,
|
||||
BetID: flag.BetID.Int64,
|
||||
OddID: flag.OddID.Int64,
|
||||
Reason: flag.Reason.String,
|
||||
FlaggedAt: flag.FlaggedAt.Time,
|
||||
Resolved: flag.Resolved.Bool,
|
||||
}
|
||||
}
|
||||
|
||||
func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams {
|
||||
return dbgen.CreateBetOutcomeParams{
|
||||
BetID: betOutcome.BetID,
|
||||
|
|
@ -140,6 +151,35 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
|
|||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error) {
|
||||
createFlag := dbgen.CreateFlagParams{
|
||||
BetID: pgtype.Int8{
|
||||
Int64: flag.BetID,
|
||||
Valid: flag.BetID != 0,
|
||||
},
|
||||
OddID: pgtype.Int8{
|
||||
Int64: flag.OddID,
|
||||
Valid: flag.OddID != 0,
|
||||
},
|
||||
Reason: pgtype.Text{
|
||||
String: flag.Reason,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
f, err := s.queries.CreateFlag(ctx, createFlag)
|
||||
if err != nil {
|
||||
domain.MongoDBLogger.Error("failed to create flag",
|
||||
zap.String("flag", f.Reason.String),
|
||||
zap.Any("flag_id", f.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.Flag{}, err
|
||||
}
|
||||
|
||||
return convertDBFlag(f), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
|
||||
bet, err := s.queries.GetBetByID(ctx, id)
|
||||
if err != nil {
|
||||
|
|
@ -237,8 +277,8 @@ func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error)
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
|
||||
func (s *Store) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||
count, err := s.queries.GetBetCountByUserID(ctx, dbgen.GetBetCountByUserIDParams{
|
||||
UserID: UserID,
|
||||
OutcomesHash: outcomesHash,
|
||||
})
|
||||
|
|
@ -250,6 +290,24 @@ func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash stri
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
|
||||
count, err := s.queries.GetBetCountByOutcomesHash(ctx, outcomesHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) {
|
||||
count, err := s.queries.GetBetOutcomeCountByOddID(ctx, oddID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
||||
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
|
||||
ID: id,
|
||||
|
|
@ -486,16 +544,16 @@ func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
|
|||
return 0, 0, 0, 0, 0, 0, fmt.Errorf("failed to get bet summary: %w", err)
|
||||
}
|
||||
|
||||
domain.MongoDBLogger.Info("GetBetSummary executed successfully",
|
||||
zap.String("query", query),
|
||||
zap.Any("args", args),
|
||||
zap.Float64("totalStakes", float64(totalStakes)), // convert if needed
|
||||
zap.Int64("totalBets", totalBets),
|
||||
zap.Int64("activeBets", activeBets),
|
||||
zap.Int64("totalWins", totalWins),
|
||||
zap.Int64("totalLosses", totalLosses),
|
||||
zap.Float64("winBalance", float64(winBalance)), // convert if needed
|
||||
)
|
||||
// domain.MongoDBLogger.Info("GetBetSummary executed successfully",
|
||||
// zap.String("query", query),
|
||||
// zap.Any("args", args),
|
||||
// zap.Float64("totalStakes", float64(totalStakes)), // convert if needed
|
||||
// zap.Int64("totalBets", totalBets),
|
||||
// zap.Int64("activeBets", activeBets),
|
||||
// zap.Int64("totalWins", totalWins),
|
||||
// zap.Int64("totalLosses", totalLosses),
|
||||
// zap.Float64("winBalance", float64(winBalance)), // convert if needed
|
||||
// )
|
||||
return totalStakes, totalBets, activeBets, totalWins, totalLosses, winBalance, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,100 +9,15 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func convertCreateBranch(branch domain.CreateBranch) dbgen.CreateBranchParams {
|
||||
return dbgen.CreateBranchParams{
|
||||
Name: branch.Name,
|
||||
Location: branch.Location,
|
||||
WalletID: branch.WalletID,
|
||||
BranchManagerID: branch.BranchManagerID,
|
||||
CompanyID: branch.CompanyID,
|
||||
IsSelfOwned: branch.IsSelfOwned,
|
||||
}
|
||||
}
|
||||
|
||||
func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
|
||||
return domain.BranchDetail{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
ManagerName: dbBranch.ManagerName.(string),
|
||||
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
|
||||
Balance: domain.Currency(dbBranch.Balance.Int64),
|
||||
IsActive: dbBranch.IsActive,
|
||||
WalletIsActive: dbBranch.WalletIsActive.Bool,
|
||||
}
|
||||
}
|
||||
|
||||
func convertDBBranch(dbBranch dbgen.Branch) domain.Branch {
|
||||
return domain.Branch{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
}
|
||||
}
|
||||
|
||||
func convertUpdateBranch(updateBranch domain.UpdateBranch) dbgen.UpdateBranchParams {
|
||||
|
||||
var newUpdateBranch dbgen.UpdateBranchParams
|
||||
|
||||
newUpdateBranch.ID = updateBranch.ID
|
||||
|
||||
if updateBranch.Name != nil {
|
||||
newUpdateBranch.Name = pgtype.Text{
|
||||
String: *updateBranch.Name,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.Location != nil {
|
||||
newUpdateBranch.Location = pgtype.Text{
|
||||
String: *updateBranch.Location,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.BranchManagerID != nil {
|
||||
newUpdateBranch.BranchManagerID = pgtype.Int8{
|
||||
Int64: *updateBranch.BranchManagerID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.CompanyID != nil {
|
||||
newUpdateBranch.CompanyID = pgtype.Int8{
|
||||
Int64: *updateBranch.CompanyID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.IsSelfOwned != nil {
|
||||
newUpdateBranch.IsSelfOwned = pgtype.Bool{
|
||||
Bool: *updateBranch.IsSelfOwned,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if updateBranch.IsActive != nil {
|
||||
newUpdateBranch.IsActive = pgtype.Bool{
|
||||
Bool: *updateBranch.IsActive,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
return newUpdateBranch
|
||||
}
|
||||
|
||||
func (s *Store) CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) {
|
||||
|
||||
dbBranch, err := s.queries.CreateBranch(ctx, convertCreateBranch(branch))
|
||||
dbBranch, err := s.queries.CreateBranch(ctx, domain.ConvertCreateBranch(branch))
|
||||
|
||||
if err != nil {
|
||||
return domain.Branch{}, err
|
||||
}
|
||||
return convertDBBranch(dbBranch), nil
|
||||
return domain.ConvertDBBranch(dbBranch), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) {
|
||||
|
|
@ -110,7 +25,7 @@ func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetai
|
|||
if err != nil {
|
||||
return domain.BranchDetail{}, err
|
||||
}
|
||||
return convertDBBranchDetail(dbBranch), nil
|
||||
return domain.ConvertDBBranchDetail(dbBranch), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) {
|
||||
|
|
@ -120,7 +35,7 @@ func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64)
|
|||
}
|
||||
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
|
||||
for _, dbBranch := range dbBranches {
|
||||
branches = append(branches, convertDBBranchDetail(dbBranch))
|
||||
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
|
@ -131,7 +46,7 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do
|
|||
}
|
||||
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
|
||||
for _, dbBranch := range dbBranches {
|
||||
branches = append(branches, convertDBBranchDetail(dbBranch))
|
||||
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
|
@ -164,7 +79,7 @@ func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter)
|
|||
}
|
||||
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
|
||||
for _, dbBranch := range dbBranches {
|
||||
branches = append(branches, convertDBBranchDetail(dbBranch))
|
||||
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
|
@ -177,18 +92,18 @@ func (s *Store) SearchBranchByName(ctx context.Context, name string) ([]domain.B
|
|||
|
||||
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
|
||||
for _, dbBranch := range dbBranches {
|
||||
branches = append(branches, convertDBBranchDetail(dbBranch))
|
||||
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) {
|
||||
|
||||
dbBranch, err := s.queries.UpdateBranch(ctx, convertUpdateBranch(branch))
|
||||
dbBranch, err := s.queries.UpdateBranch(ctx, domain.ConvertUpdateBranch(branch))
|
||||
if err != nil {
|
||||
return domain.Branch{}, err
|
||||
}
|
||||
return convertDBBranch(dbBranch), nil
|
||||
return domain.ConvertDBBranch(dbBranch), nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteBranch(ctx context.Context, id int64) error {
|
||||
|
|
@ -272,7 +187,7 @@ func (s *Store) GetBranchByCashier(ctx context.Context, userID int64) (domain.Br
|
|||
return domain.Branch{}, err
|
||||
}
|
||||
|
||||
return convertDBBranch(branch), err
|
||||
return domain.ConvertDBBranch(branch), err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
|
|||
StartTime: e.StartTime.Time.UTC(),
|
||||
Source: e.Source.String,
|
||||
Status: domain.EventStatus(e.Status.String),
|
||||
Flagged: e.Flagged,
|
||||
IsFeatured: e.IsFeatured,
|
||||
}
|
||||
}
|
||||
return upcomingEvents, nil
|
||||
|
|
@ -122,7 +122,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
|
|||
StartTime: e.StartTime.Time.UTC(),
|
||||
Source: e.Source.String,
|
||||
Status: domain.EventStatus(e.Status.String),
|
||||
Flagged: e.Flagged,
|
||||
IsFeatured: e.IsFeatured,
|
||||
IsActive: e.IsActive,
|
||||
}
|
||||
}
|
||||
return upcomingEvents, nil
|
||||
|
|
@ -139,6 +140,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
Int32: int32(filter.SportID.Value),
|
||||
Valid: filter.SportID.Valid,
|
||||
},
|
||||
Query: pgtype.Text{
|
||||
String: filter.Query.Value,
|
||||
Valid: filter.Query.Valid,
|
||||
},
|
||||
Limit: pgtype.Int4{
|
||||
Int32: int32(filter.Limit.Value),
|
||||
Valid: filter.Limit.Valid,
|
||||
|
|
@ -159,9 +164,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
String: filter.CountryCode.Value,
|
||||
Valid: filter.CountryCode.Valid,
|
||||
},
|
||||
Flagged: pgtype.Bool{
|
||||
Bool: filter.Flagged.Valid,
|
||||
Valid: filter.Flagged.Valid,
|
||||
IsFeatured: pgtype.Bool{
|
||||
Bool: filter.Featured.Valid,
|
||||
Valid: filter.Featured.Valid,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -186,7 +191,8 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
StartTime: e.StartTime.Time.UTC(),
|
||||
Source: e.Source.String,
|
||||
Status: domain.EventStatus(e.Status.String),
|
||||
Flagged: e.Flagged,
|
||||
IsFeatured: e.IsFeatured,
|
||||
IsActive: e.IsActive,
|
||||
}
|
||||
}
|
||||
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
|
||||
|
|
@ -198,6 +204,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
Int32: int32(filter.SportID.Value),
|
||||
Valid: filter.SportID.Valid,
|
||||
},
|
||||
Query: pgtype.Text{
|
||||
String: filter.Query.Value,
|
||||
Valid: filter.Query.Valid,
|
||||
},
|
||||
FirstStartTime: pgtype.Timestamp{
|
||||
Time: filter.FirstStartTime.Value.UTC(),
|
||||
Valid: filter.FirstStartTime.Valid,
|
||||
|
|
@ -210,9 +220,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
String: filter.CountryCode.Value,
|
||||
Valid: filter.CountryCode.Valid,
|
||||
},
|
||||
Flagged: pgtype.Bool{
|
||||
Bool: filter.Flagged.Valid,
|
||||
Valid: filter.Flagged.Valid,
|
||||
IsFeatured: pgtype.Bool{
|
||||
Bool: filter.Featured.Valid,
|
||||
Valid: filter.Featured.Valid,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -244,7 +254,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
|
|||
StartTime: event.StartTime.Time.UTC(),
|
||||
Source: event.Source.String,
|
||||
Status: domain.EventStatus(event.Status.String),
|
||||
Flagged: event.Flagged,
|
||||
IsFeatured: event.IsFeatured,
|
||||
}, nil
|
||||
}
|
||||
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
|
||||
|
|
@ -280,10 +290,10 @@ func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status do
|
|||
|
||||
}
|
||||
|
||||
func (s *Store) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
|
||||
return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{
|
||||
func (s *Store) UpdateFeatured(ctx context.Context, eventID string, isFeatured bool) error {
|
||||
return s.queries.UpdateFeatured(ctx, dbgen.UpdateFeaturedParams{
|
||||
ID: eventID,
|
||||
Flagged: flagged,
|
||||
IsFeatured: isFeatured,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ func (s *Store) UpdateLeague(ctx context.Context, league domain.UpdateLeague) er
|
|||
},
|
||||
IsFeatured: pgtype.Bool{
|
||||
Bool: league.IsFeatured.Value,
|
||||
Valid: league.IsActive.Valid,
|
||||
Valid: league.IsFeatured.Valid,
|
||||
},
|
||||
SportID: pgtype.Int4{
|
||||
Int32: league.SportID.Value,
|
||||
|
|
|
|||
30
internal/repository/location.go
Normal file
30
internal/repository/location.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
|
||||
func (s *Store) GetAllBranchLocations (ctx context.Context, query domain.ValidString) ([]domain.BranchLocation, error) {
|
||||
locations, err := s.queries.GetAllBranchLocations(ctx, pgtype.Text{
|
||||
String: query.Value,
|
||||
Valid: query.Valid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []domain.BranchLocation = make([]domain.BranchLocation, 0, len(locations))
|
||||
|
||||
for _, location := range locations {
|
||||
result = append(result, domain.BranchLocation{
|
||||
Key: location.Key,
|
||||
Name: location.Value,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
@ -317,39 +317,7 @@ func (s *Store) CountUnreadNotifications(ctx context.Context, userID int64) (int
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||
dbCompany, err := s.queries.GetCompanyByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Company{}, err
|
||||
}
|
||||
|
||||
return domain.Company{
|
||||
ID: dbCompany.ID,
|
||||
Name: dbCompany.Name,
|
||||
AdminID: dbCompany.AdminID,
|
||||
WalletID: dbCompany.WalletID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||
dbBranch, err := s.queries.GetBranchByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Branch{}, err
|
||||
}
|
||||
|
||||
return domain.Branch{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
IsActive: dbBranch.IsActive,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
// Creat: dbBranch.CreatedAt.Time,
|
||||
// UpdatedAt: dbBranch.UpdatedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||
// dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -14,6 +15,10 @@ func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
|
|||
var dbSettingList domain.DBSettingList
|
||||
|
||||
var int64SettingsMap = domain.ConvertInt64SettingsMap(&dbSettingList)
|
||||
var stringSettingsMap = domain.ConvertStringSettingsMap(&dbSettingList)
|
||||
var boolSettingsMap = domain.ConvertBoolSettingsMap(&dbSettingList)
|
||||
var float32SettingsMap = domain.ConvertFloat32SettingsMap(&dbSettingList)
|
||||
var timeSettingsMap = domain.ConvertTimeSettingsMap(&dbSettingList)
|
||||
|
||||
for _, setting := range settings {
|
||||
is_setting_unknown := true
|
||||
|
|
@ -31,6 +36,57 @@ func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
|
|||
}
|
||||
}
|
||||
|
||||
for key, dbSetting := range stringSettingsMap {
|
||||
if setting.Key == key {
|
||||
*dbSetting = domain.ValidString{
|
||||
Value: setting.Value,
|
||||
Valid: true,
|
||||
}
|
||||
is_setting_unknown = false
|
||||
}
|
||||
}
|
||||
|
||||
for key, dbSetting := range boolSettingsMap {
|
||||
if setting.Key == key {
|
||||
value, err := strconv.ParseBool(setting.Value)
|
||||
if err != nil {
|
||||
return domain.SettingList{}, err
|
||||
}
|
||||
*dbSetting = domain.ValidBool{
|
||||
Value: value,
|
||||
Valid: true,
|
||||
}
|
||||
is_setting_unknown = false
|
||||
}
|
||||
}
|
||||
|
||||
for key, dbSetting := range float32SettingsMap {
|
||||
if setting.Key == key {
|
||||
value, err := strconv.ParseFloat(setting.Value, 32)
|
||||
if err != nil {
|
||||
return domain.SettingList{}, err
|
||||
}
|
||||
*dbSetting = domain.ValidFloat32{
|
||||
Value: float32(value),
|
||||
Valid: true,
|
||||
}
|
||||
is_setting_unknown = false
|
||||
}
|
||||
}
|
||||
for key, dbSetting := range timeSettingsMap {
|
||||
if setting.Key == key {
|
||||
value, err := time.Parse(time.RFC3339, setting.Value)
|
||||
if err != nil {
|
||||
return domain.SettingList{}, err
|
||||
}
|
||||
*dbSetting = domain.ValidTime{
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -490,6 +490,27 @@ func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User, is_c
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error) {
|
||||
userRes, err := s.queries.GetAdminByCompanyID(ctx, companyID)
|
||||
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
return domain.User{
|
||||
ID: userRes.ID,
|
||||
FirstName: userRes.FirstName,
|
||||
LastName: userRes.LastName,
|
||||
Email: userRes.Email.String,
|
||||
PhoneNumber: userRes.PhoneNumber.String,
|
||||
Role: domain.Role(userRes.Role),
|
||||
EmailVerified: userRes.EmailVerified,
|
||||
PhoneVerified: userRes.PhoneVerified,
|
||||
CreatedAt: userRes.CreatedAt.Time,
|
||||
UpdatedAt: userRes.UpdatedAt.Time,
|
||||
Suspended: userRes.Suspended,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomerCounts returns total and active customer counts
|
||||
func (s *Store) GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
||||
query := `SELECT
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func convertDBWallet(wallet dbgen.Wallet) domain.Wallet {
|
|||
IsTransferable: wallet.IsTransferable,
|
||||
IsActive: wallet.IsActive,
|
||||
UserID: wallet.UserID,
|
||||
Type: domain.WalletType(wallet.Type),
|
||||
UpdatedAt: wallet.UpdatedAt.Time,
|
||||
CreatedAt: wallet.CreatedAt.Time,
|
||||
}
|
||||
|
|
@ -28,6 +29,7 @@ func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams {
|
|||
IsBettable: wallet.IsBettable,
|
||||
IsTransferable: wallet.IsTransferable,
|
||||
UserID: wallet.UserID,
|
||||
Type: string(wallet.Type),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +185,40 @@ func (s *Store) UpdateWalletActive(ctx context.Context, id int64, isActive bool)
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||
dbCompany, err := s.queries.GetCompanyByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Company{}, err
|
||||
}
|
||||
|
||||
return domain.Company{
|
||||
ID: dbCompany.ID,
|
||||
Name: dbCompany.Name,
|
||||
AdminID: dbCompany.AdminID,
|
||||
WalletID: dbCompany.WalletID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||
dbBranch, err := s.queries.GetBranchByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Branch{}, err
|
||||
}
|
||||
|
||||
return domain.Branch{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
IsActive: dbBranch.IsActive,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
// Creat: dbBranch.CreatedAt.Time,
|
||||
// UpdatedAt: dbBranch.UpdatedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBalanceSummary returns wallet balance summary
|
||||
func (s *Store) GetBalanceSummary(ctx context.Context, filter domain.ReportFilter) (domain.BalanceSummary, error) {
|
||||
var summary domain.BalanceSummary
|
||||
|
|
@ -275,4 +311,3 @@ func (s *Store) GetTotalWallets(ctx context.Context, filter domain.ReportFilter)
|
|||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,16 @@ import (
|
|||
type BetStore interface {
|
||||
CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error)
|
||||
CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error)
|
||||
CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error)
|
||||
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
|
||||
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
|
||||
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
|
||||
GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, 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)
|
||||
GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error)
|
||||
GetBetCountByUserID(ctx context.Context, userID int64, outcomesHash string) (int64, error)
|
||||
GetBetCountByOutcomesHash(ctx context.Context, 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)
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"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/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
|
@ -54,6 +55,7 @@ type Service struct {
|
|||
branchSvc branch.Service
|
||||
companySvc company.Service
|
||||
settingSvc settings.Service
|
||||
userSvc user.Service
|
||||
notificationSvc *notificationservice.Service
|
||||
logger *slog.Logger
|
||||
mongoLogger *zap.Logger
|
||||
|
|
@ -67,6 +69,7 @@ func NewService(
|
|||
branchSvc branch.Service,
|
||||
companySvc company.Service,
|
||||
settingSvc settings.Service,
|
||||
userSvc user.Service,
|
||||
notificationSvc *notificationservice.Service,
|
||||
logger *slog.Logger,
|
||||
mongoLogger *zap.Logger,
|
||||
|
|
@ -215,6 +218,9 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
|
|||
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role, companyID domain.ValidInt64) (domain.CreateBetRes, error) {
|
||||
settingsList, err := s.settingSvc.GetSettingList(ctx)
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
if req.Amount < 1 {
|
||||
return domain.CreateBetRes{}, ErrInvalidAmount
|
||||
}
|
||||
|
|
@ -269,7 +275,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
count, err := s.GetBetCount(ctx, userID, outcomesHash)
|
||||
count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to generate cashout ID",
|
||||
zap.Int64("user_id", userID),
|
||||
|
|
@ -398,6 +404,79 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
for i := range outcomes {
|
||||
// flag odds with large amount of users betting on them
|
||||
count, err := s.betStore.GetBetOutcomeCountByOddID(ctx, outcomes[i].OddID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get count of bet outcome",
|
||||
zap.Int64("bet_id", bet.ID),
|
||||
zap.Int64("odd_id", outcomes[i].OddID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
// TODO: fetch cap from settings in db
|
||||
if count > 20 {
|
||||
flag := domain.CreateFlagReq{
|
||||
BetID: 0,
|
||||
OddID: outcomes[i].OddID,
|
||||
Reason: fmt.Sprintf("too many users targeting odd - (%d)", outcomes[i].OddID),
|
||||
}
|
||||
|
||||
_, err := s.betStore.CreateFlag(ctx, flag)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to create flag for bet",
|
||||
zap.Int64("bet_id", bet.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flag bets that have more than three outcomes
|
||||
if len(outcomes) > 3 {
|
||||
flag := domain.CreateFlagReq{
|
||||
BetID: bet.ID,
|
||||
OddID: 0,
|
||||
Reason: fmt.Sprintf("too many outcomes - (%d)", len(outcomes)),
|
||||
}
|
||||
|
||||
_, err := s.betStore.CreateFlag(ctx, flag)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to create flag for bet",
|
||||
zap.Int64("bet_id", bet.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// large amount of users betting on the same bet_outcomes
|
||||
total_bet_count, err := s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get bet outcomes count",
|
||||
zap.String("outcomes_hash", outcomesHash),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
if total_bet_count > 10 {
|
||||
flag := domain.CreateFlagReq{
|
||||
BetID: bet.ID,
|
||||
OddID: 0,
|
||||
Reason: fmt.Sprintf("too many users bet on same outcomes - (%s)", outcomesHash),
|
||||
}
|
||||
_, err := s.betStore.CreateFlag(ctx, flag)
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get bet outcomes count",
|
||||
zap.String("outcomes_hash", outcomesHash),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
res := domain.ConvertCreateBet(bet, rows)
|
||||
|
||||
return res, nil
|
||||
|
|
@ -417,7 +496,7 @@ func (s *Service) DeductBetFromBranchWallet(ctx context.Context, amount float32,
|
|||
|
||||
deductedAmount := amount * company.DeductedPercentage
|
||||
_, err = s.walletSvc.DeductFromWallet(ctx,
|
||||
walletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{
|
||||
walletID, domain.ToCurrency(deductedAmount), domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: true,
|
||||
}, domain.TRANSFER_DIRECT,
|
||||
|
|
@ -446,7 +525,7 @@ func (s *Service) DeductBetFromCustomerWallet(ctx context.Context, amount float3
|
|||
}
|
||||
if amount < wallets.RegularBalance.Float32() {
|
||||
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
|
||||
domain.ToCurrency(amount), domain.CustomerWalletType, domain.ValidInt64{},
|
||||
domain.ToCurrency(amount), domain.ValidInt64{},
|
||||
domain.TRANSFER_DIRECT, fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", amount))
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
|
||||
|
|
@ -465,7 +544,7 @@ func (s *Service) DeductBetFromCustomerWallet(ctx context.Context, amount float3
|
|||
}
|
||||
// Empty the regular balance
|
||||
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
|
||||
wallets.RegularBalance, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
wallets.RegularBalance, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", wallets.RegularBalance.Float32()))
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
|
||||
|
|
@ -480,7 +559,7 @@ func (s *Service) DeductBetFromCustomerWallet(ctx context.Context, amount float3
|
|||
// Empty remaining from static balance
|
||||
remainingAmount := wallets.RegularBalance - domain.Currency(amount)
|
||||
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.StaticID,
|
||||
remainingAmount, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
remainingAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", remainingAmount.Float32()))
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("wallet deduction failed for customer static wallet",
|
||||
|
|
@ -716,7 +795,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
|
|||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
count, err := s.GetBetCount(ctx, userID, outcomesHash)
|
||||
count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get bet count",
|
||||
zap.Int64("user_id", userID),
|
||||
|
|
@ -799,8 +878,12 @@ func (s *Service) GetBetByFastCode(ctx context.Context, fastcode string) (domain
|
|||
return s.betStore.GetBetByFastCode(ctx, fastcode)
|
||||
}
|
||||
|
||||
func (s *Service) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||
return s.betStore.GetBetCount(ctx, UserID, outcomesHash)
|
||||
func (s *Service) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||
return s.betStore.GetBetCountByUserID(ctx, UserID, outcomesHash)
|
||||
}
|
||||
|
||||
func (s *Service) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
|
||||
return s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
||||
|
|
@ -817,10 +900,19 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
return err
|
||||
}
|
||||
|
||||
if bet.IsShopBet ||
|
||||
status == domain.OUTCOME_STATUS_ERROR ||
|
||||
status == domain.OUTCOME_STATUS_PENDING ||
|
||||
status == domain.OUTCOME_STATUS_LOSS {
|
||||
switch {
|
||||
case bet.IsShopBet:
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
case status == domain.OUTCOME_STATUS_ERROR, status == domain.OUTCOME_STATUS_PENDING:
|
||||
s.SendErrorStatusNotification(ctx, status, bet.UserID, "")
|
||||
s.SendAdminErrorAlertNotification(ctx, status, "")
|
||||
s.mongoLogger.Error("Bet Status is error",
|
||||
zap.Int64("bet_id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
case status == domain.OUTCOME_STATUS_LOSS:
|
||||
s.SendLosingStatusNotification(ctx, status, bet.UserID, "")
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
}
|
||||
|
||||
|
|
@ -837,10 +929,15 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
switch status {
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds)
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) / 2
|
||||
default:
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
amount = bet.Amount
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
default:
|
||||
return fmt.Errorf("invalid outcome status")
|
||||
}
|
||||
|
||||
_, err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount, domain.ValidInt64{},
|
||||
|
|
@ -858,6 +955,207 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
}
|
||||
|
||||
func (s *Service) SendWinningStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, winningAmount domain.Currency, extra string) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
headline = "You Bet Has Won!"
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
)
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
headline = "You have a half win"
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
)
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
headline = "Your bet has been refunded"
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
)
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"winning_amount":%.2f,
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, winningAmount.Float32(), status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendLosingStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_LOSS:
|
||||
headline = "Your bet has lost"
|
||||
message = "Better luck next time"
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING:
|
||||
headline = "There was an error with your bet"
|
||||
message = "We have encounter an error with your bet. We will fix it as soon as we can"
|
||||
}
|
||||
|
||||
errorSeverityLevel := domain.NotificationErrorSeverityFatal
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 1,
|
||||
ErrorSeverity: &errorSeverityLevel,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status domain.OutcomeStatus, extra string) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING:
|
||||
headline = "There was an error with your bet"
|
||||
message = "We have encounter an error with your bet. We will fix it as soon as we can"
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelEmail,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
}
|
||||
|
||||
users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
|
||||
Role: string(domain.RoleAdmin),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get admin recipients",
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
betNotification.RecipientID = user.ID
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send admin notification",
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send email admin notification",
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) {
|
||||
betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID)
|
||||
if err != nil {
|
||||
|
|
@ -1033,7 +1331,7 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error {
|
|||
)
|
||||
continue
|
||||
}
|
||||
cashbackAmount := math.Min(float64(settingsList.CashbackAmountCap), float64(calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)))
|
||||
cashbackAmount := math.Min(float64(settingsList.CashbackAmountCap.Float32()), float64(calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)))
|
||||
|
||||
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(cashbackAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
domain.PaymentDetails{}, fmt.Sprintf("cashback amount of %f added to users static wallet", cashbackAmount))
|
||||
|
|
|
|||
11
internal/services/branch/branch_locations.go
Normal file
11
internal/services/branch/branch_locations.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package branch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Service) GetAllBranchLocations(ctx context.Context, query domain.ValidString) ([]domain.BranchLocation, error) {
|
||||
return s.branchStore.GetAllBranchLocations(ctx, query)
|
||||
}
|
||||
|
|
@ -29,4 +29,6 @@ type BranchStore interface {
|
|||
|
||||
GetAllCompaniesBranch(ctx context.Context) ([]domain.Company, error)
|
||||
GetBranchesByCompany(ctx context.Context, companyID int64) ([]domain.Branch, error)
|
||||
|
||||
GetAllBranchLocations(ctx context.Context, query domain.ValidString) ([]domain.BranchLocation, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,3 +78,4 @@ func (s *Service) GetAllCompaniesBranch(ctx context.Context) ([]domain.Company,
|
|||
func (s *Service) GetBranchesByCompany(ctx context.Context, companyID int64) ([]domain.Branch, error) {
|
||||
return s.branchStore.GetBranchesByCompany(ctx, companyID)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ type Service interface {
|
|||
// GetAndStoreMatchResult(ctx context.Context, eventID string) error
|
||||
UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error
|
||||
UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error
|
||||
UpdateFlagged(ctx context.Context, eventID string, flagged bool) error
|
||||
UpdateFeatured(ctx context.Context, eventID string, flagged bool) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,8 +369,8 @@ func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status
|
|||
return s.store.UpdateEventStatus(ctx, eventID, status)
|
||||
}
|
||||
|
||||
func (s *service) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
|
||||
return s.store.UpdateFlagged(ctx, eventID, flagged)
|
||||
func (s *service) UpdateFeatured(ctx context.Context, eventID string, flagged bool) error {
|
||||
return s.store.UpdateFeatured(ctx, eventID, flagged)
|
||||
}
|
||||
|
||||
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {
|
||||
|
|
|
|||
26
internal/services/messenger/email.go
Normal file
26
internal/services/messenger/email.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package messenger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/resend/resend-go/v2"
|
||||
|
||||
)
|
||||
|
||||
func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, subject string) error {
|
||||
apiKey := s.config.ResendApiKey
|
||||
client := resend.NewClient(apiKey)
|
||||
formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">"
|
||||
params := &resend.SendEmailRequest{
|
||||
From: formattedSenderEmail,
|
||||
To: []string{receiverEmail},
|
||||
Subject: subject,
|
||||
Text: message,
|
||||
}
|
||||
|
||||
_, err := client.Emails.Send(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
21
internal/services/messenger/service.go
Normal file
21
internal/services/messenger/service.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package messenger
|
||||
|
||||
import (
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
settingSvc *settings.Service
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewService(
|
||||
settingSvc *settings.Service,
|
||||
cfg *config.Config,
|
||||
) *Service {
|
||||
return &Service{
|
||||
settingSvc: settingSvc,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
85
internal/services/messenger/sms.go
Normal file
85
internal/services/messenger/sms.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package messenger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/twilio/twilio-go"
|
||||
twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSMSProviderNotFound = errors.New("SMS Provider Not Found")
|
||||
)
|
||||
|
||||
func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string) error {
|
||||
|
||||
settingsList, err := s.settingSvc.GetSettingList(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch settingsList.SMSProvider {
|
||||
case domain.AfroMessage:
|
||||
return s.SendAfroMessageSMS(ctx, receiverPhone, message)
|
||||
case domain.TwilioSms:
|
||||
return s.SendTwilioSMS(ctx, receiverPhone, message)
|
||||
default:
|
||||
return ErrSMSProviderNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendAfroMessageSMS(ctx context.Context, receiverPhone, message string) error {
|
||||
apiKey := s.config.AFRO_SMS_API_KEY
|
||||
senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||
hostURL := s.config.ADRO_SMS_HOST_URL
|
||||
endpoint := "/api/send"
|
||||
|
||||
// API endpoint has been updated
|
||||
// TODO: no need for package for the afro message operations (pretty simple stuff)
|
||||
request := afro.GetRequest(apiKey, endpoint, hostURL)
|
||||
request.BaseURL = "https://api.afromessage.com/api/send"
|
||||
|
||||
request.Method = "GET"
|
||||
request.Sender(senderName)
|
||||
request.To(receiverPhone, message)
|
||||
|
||||
response, err := afro.MakeRequestWithContext(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response["acknowledge"] == "success" {
|
||||
return nil
|
||||
} else {
|
||||
fmt.Println(response["response"].(map[string]interface{}))
|
||||
return errors.New("SMS delivery failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendTwilioSMS(ctx context.Context, receiverPhone, message string) error {
|
||||
accountSid := s.config.TwilioAccountSid
|
||||
authToken := s.config.TwilioAuthToken
|
||||
senderPhone := s.config.TwilioSenderPhoneNumber
|
||||
|
||||
client := twilio.NewRestClientWithParams(twilio.ClientParams{
|
||||
Username: accountSid,
|
||||
Password: authToken,
|
||||
})
|
||||
|
||||
params := &twilioApi.CreateMessageParams{}
|
||||
params.SetTo(receiverPhone)
|
||||
params.SetFrom(senderPhone)
|
||||
params.SetBody(message)
|
||||
|
||||
_, err := client.Api.CreateMessage(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", "Error sending SMS message: %s"+err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ import (
|
|||
)
|
||||
|
||||
type NotificationStore interface {
|
||||
GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||
GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||
SendNotification(ctx context.Context, notification *domain.Notification) error
|
||||
MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error
|
||||
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
||||
646
internal/services/notification/service.go
Normal file
646
internal/services/notification/service.go
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
package notificationservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
// "errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"go.uber.org/zap"
|
||||
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||
// afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo repository.NotificationRepository
|
||||
Hub *ws.NotificationHub
|
||||
notificationStore NotificationStore
|
||||
connections sync.Map
|
||||
notificationCh chan *domain.Notification
|
||||
stopCh chan struct{}
|
||||
config *config.Config
|
||||
userSvc *user.Service
|
||||
messengerSvc *messenger.Service
|
||||
mongoLogger *zap.Logger
|
||||
logger *slog.Logger
|
||||
redisClient *redis.Client
|
||||
}
|
||||
|
||||
func New(repo repository.NotificationRepository,
|
||||
mongoLogger *zap.Logger,
|
||||
logger *slog.Logger,
|
||||
cfg *config.Config,
|
||||
messengerSvc *messenger.Service,
|
||||
userSvc *user.Service,
|
||||
) *Service {
|
||||
hub := ws.NewNotificationHub()
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisAddr, // e.g., "redis:6379"
|
||||
})
|
||||
|
||||
svc := &Service{
|
||||
repo: repo,
|
||||
Hub: hub,
|
||||
mongoLogger: mongoLogger,
|
||||
logger: logger,
|
||||
connections: sync.Map{},
|
||||
notificationCh: make(chan *domain.Notification, 1000),
|
||||
stopCh: make(chan struct{}),
|
||||
messengerSvc: messengerSvc,
|
||||
userSvc: userSvc,
|
||||
config: cfg,
|
||||
redisClient: rdb,
|
||||
}
|
||||
|
||||
go hub.Run()
|
||||
go svc.startWorker()
|
||||
go svc.startRetryWorker()
|
||||
go svc.RunRedisSubscriber(context.Background())
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (s *Service) addConnection(recipientID int64, c *websocket.Conn) error {
|
||||
if c == nil {
|
||||
s.mongoLogger.Warn("[NotificationSvc.AddConnection] Attempted to add nil WebSocket connection",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fmt.Errorf("Invalid Websocket Connection")
|
||||
}
|
||||
|
||||
s.connections.Store(recipientID, c)
|
||||
s.mongoLogger.Info("[NotificationSvc.AddConnection] Added WebSocket connection",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendNotification(ctx context.Context, notification *domain.Notification) error {
|
||||
notification.ID = helpers.GenerateID()
|
||||
notification.Timestamp = time.Now()
|
||||
notification.DeliveryStatus = domain.DeliveryStatusPending
|
||||
|
||||
created, err := s.repo.CreateNotification(ctx, notification)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.SendNotification] Failed to create notification",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
notification = created
|
||||
|
||||
if notification.DeliveryChannel == domain.DeliveryChannelInApp {
|
||||
s.Hub.Broadcast <- map[string]interface{}{
|
||||
"type": "CREATED_NOTIFICATION",
|
||||
"recipient_id": notification.RecipientID,
|
||||
"payload": notification,
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case s.notificationCh <- notification:
|
||||
default:
|
||||
s.mongoLogger.Warn("[NotificationSvc.SendNotification] Notification channel full, dropping notification",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error {
|
||||
for _, notificationID := range notificationIDs {
|
||||
_, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read",
|
||||
zap.String("notificationID", notificationID),
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// count, err := s.repo.CountUnreadNotifications(ctx, recipientID)
|
||||
// if err != nil {
|
||||
// s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// s.Hub.Broadcast <- map[string]interface{}{
|
||||
// "type": "COUNT_NOT_OPENED_NOTIFICATION",
|
||||
// "recipient_id": recipientID,
|
||||
// "payload": map[string]int{
|
||||
// "not_opened_notifications_count": int(count),
|
||||
// },
|
||||
// }
|
||||
|
||||
s.mongoLogger.Info("[NotificationSvc.MarkAsRead] Notification marked as read",
|
||||
zap.String("notificationID", notificationID),
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error) {
|
||||
notifications, err := s.repo.ListNotifications(ctx, recipientID, limit, offset)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.ListNotifications] Failed to list notifications",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Int("limit", limit),
|
||||
zap.Int("offset", offset),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
s.mongoLogger.Info("[NotificationSvc.ListNotifications] Successfully listed notifications",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Int("count", len(notifications)),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||
notifications, err := s.repo.GetAllNotifications(ctx, limit, offset)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.ListNotifications] Failed to get all notifications",
|
||||
zap.Int("limit", limit),
|
||||
zap.Int("offset", offset),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
s.mongoLogger.Info("[NotificationSvc.ListNotifications] Successfully retrieved all notifications",
|
||||
zap.Int("count", len(notifications)),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *websocket.Conn) error {
|
||||
err := s.addConnection(recipientID, c)
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.ConnectWebSocket] Failed to create WebSocket connection",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
s.mongoLogger.Info("[NotificationSvc.ConnectWebSocket] WebSocket connection established",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) DisconnectWebSocket(recipientID int64) {
|
||||
if conn, loaded := s.connections.LoadAndDelete(recipientID); loaded {
|
||||
conn.(*websocket.Conn).Close()
|
||||
// s.logger.Info("[NotificationSvc.DisconnectWebSocket] Disconnected WebSocket", "recipientID", recipientID)
|
||||
s.mongoLogger.Info("[NotificationSvc.DisconnectWebSocket] Disconnected WebSocket",
|
||||
zap.Int64("recipientID", recipientID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// func (s *Service) SendSMS(ctx context.Context, recipientID int64, message string) error {
|
||||
// s.logger.Info("[NotificationSvc.SendSMS] SMS notification requested", "recipientID", recipientID, "message", message)
|
||||
|
||||
// apiKey := s.config.AFRO_SMS_API_KEY
|
||||
// senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||
// receiverPhone := s.config.AFRO_SMS_RECEIVER_PHONE_NUMBER
|
||||
// hostURL := s.config.ADRO_SMS_HOST_URL
|
||||
// endpoint := "/api/send"
|
||||
|
||||
// request := afro.GetRequest(apiKey, endpoint, hostURL)
|
||||
// request.Method = "GET"
|
||||
// request.Sender(senderName)
|
||||
// request.To(receiverPhone, message)
|
||||
|
||||
// response, err := afro.MakeRequestWithContext(ctx, request)
|
||||
// if err != nil {
|
||||
// s.logger.Error("[NotificationSvc.SendSMS] Failed to send SMS", "recipientID", recipientID, "error", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if response["acknowledge"] == "success" {
|
||||
// s.logger.Info("[NotificationSvc.SendSMS] SMS sent successfully", "recipientID", recipientID)
|
||||
// } else {
|
||||
// s.logger.Error("[NotificationSvc.SendSMS] Failed to send SMS", "recipientID", recipientID, "response", response["response"])
|
||||
// return errors.New("SMS delivery failed: " + response["response"].(string))
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (s *Service) SendEmail(ctx context.Context, recipientID int64, subject, message string) error {
|
||||
// s.logger.Info("[NotificationSvc.SendEmail] Email notification requested", "recipientID", recipientID, "subject", subject)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *Service) startWorker() {
|
||||
for {
|
||||
select {
|
||||
case notification := <-s.notificationCh:
|
||||
s.handleNotification(notification)
|
||||
case <-s.stopCh:
|
||||
s.mongoLogger.Info("[NotificationSvc.StartWorker] Worker stopped",
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
|
||||
return s.repo.ListRecipientIDs(ctx, receiver)
|
||||
}
|
||||
|
||||
func (s *Service) handleNotification(notification *domain.Notification) {
|
||||
ctx := context.Background()
|
||||
|
||||
switch notification.DeliveryChannel {
|
||||
case domain.DeliveryChannelSMS:
|
||||
err := s.SendNotificationSMS(ctx, notification.RecipientID, notification.Payload.Message)
|
||||
if err != nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
} else {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
}
|
||||
|
||||
case domain.DeliveryChannelEmail:
|
||||
err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message)
|
||||
if err != nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
} else {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
}
|
||||
default:
|
||||
if notification.DeliveryChannel != domain.DeliveryChannelInApp {
|
||||
s.mongoLogger.Warn("[NotificationSvc.HandleNotification] Unsupported delivery channel",
|
||||
zap.String("channel", string(notification.DeliveryChannel)),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to update notification status",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, message string) error {
|
||||
// Get User Phone Number
|
||||
user, err := s.userSvc.GetUserByID(ctx, recipientID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !user.PhoneVerified {
|
||||
return fmt.Errorf("Cannot send notification to unverified phone number")
|
||||
}
|
||||
|
||||
if user.PhoneNumber == "" {
|
||||
return fmt.Errorf("Phone Number is invalid")
|
||||
}
|
||||
err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64, message string, subject string) error {
|
||||
// Get User Phone Number
|
||||
user, err := s.userSvc.GetUserByID(ctx, recipientID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !user.EmailVerified {
|
||||
return fmt.Errorf("Cannot send notification to unverified email")
|
||||
}
|
||||
|
||||
if user.PhoneNumber == "" {
|
||||
return fmt.Errorf("Email is invalid")
|
||||
}
|
||||
err = s.messengerSvc.SendEmail(ctx, user.PhoneNumber, message, subject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) startRetryWorker() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.retryFailedNotifications()
|
||||
case <-s.stopCh:
|
||||
s.mongoLogger.Info("[NotificationSvc.StartRetryWorker] Retry worker stopped",
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) retryFailedNotifications() {
|
||||
ctx := context.Background()
|
||||
failedNotifications, err := s.repo.ListFailedNotifications(ctx, 100)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to list failed notifications",
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range failedNotifications {
|
||||
notification := &n
|
||||
go func(notification *domain.Notification) {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
switch notification.DeliveryChannel {
|
||||
case domain.DeliveryChannelSMS:
|
||||
if err := s.SendNotificationSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
} else {
|
||||
s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
case domain.DeliveryChannelEmail:
|
||||
if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
} else {
|
||||
s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Max retries reached for notification",
|
||||
zap.String("id", notification.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}(notification)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
|
||||
return s.repo.CountUnreadNotifications(ctx, recipient_id)
|
||||
}
|
||||
|
||||
// func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){
|
||||
// return s.repo.Get(ctx, filter)
|
||||
// }
|
||||
|
||||
func (s *Service) RunRedisSubscriber(ctx context.Context) {
|
||||
pubsub := s.redisClient.Subscribe(ctx, "live_metrics")
|
||||
defer pubsub.Close()
|
||||
|
||||
ch := pubsub.Channel()
|
||||
for msg := range ch {
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(msg.Payload), &parsed); err != nil {
|
||||
// s.logger.Error("invalid Redis message format", "payload", msg.Payload, "error", err)
|
||||
s.mongoLogger.Error("invalid Redis message format",
|
||||
zap.String("payload", msg.Payload),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
eventType, _ := parsed["type"].(string)
|
||||
payload := parsed["payload"]
|
||||
recipientID, hasRecipient := parsed["recipient_id"]
|
||||
recipientType, _ := parsed["recipient_type"].(string)
|
||||
|
||||
message := map[string]interface{}{
|
||||
"type": eventType,
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
if hasRecipient {
|
||||
message["recipient_id"] = recipientID
|
||||
message["recipient_type"] = recipientType
|
||||
}
|
||||
|
||||
s.Hub.Broadcast <- message
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) UpdateLiveWalletMetrics(ctx context.Context, companies []domain.GetCompany, branches []domain.BranchWallet) error {
|
||||
const key = "live_metrics"
|
||||
|
||||
companyBalances := make([]domain.CompanyWalletBalance, 0, len(companies))
|
||||
for _, c := range companies {
|
||||
companyBalances = append(companyBalances, domain.CompanyWalletBalance{
|
||||
CompanyID: c.ID,
|
||||
CompanyName: c.Name,
|
||||
Balance: float64(c.WalletBalance.Float32()),
|
||||
})
|
||||
}
|
||||
|
||||
branchBalances := make([]domain.BranchWalletBalance, 0, len(branches))
|
||||
for _, b := range branches {
|
||||
branchBalances = append(branchBalances, domain.BranchWalletBalance{
|
||||
BranchID: b.ID,
|
||||
BranchName: b.Name,
|
||||
CompanyID: b.CompanyID,
|
||||
Balance: float64(b.Balance.Float32()),
|
||||
})
|
||||
}
|
||||
|
||||
payload := domain.LiveWalletMetrics{
|
||||
Timestamp: time.Now(),
|
||||
CompanyBalances: companyBalances,
|
||||
BranchBalances: branchBalances,
|
||||
}
|
||||
|
||||
updatedData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.redisClient.Set(ctx, key, updatedData, 0).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.redisClient.Publish(ctx, key, updatedData).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error) {
|
||||
const key = "live_metrics"
|
||||
var metric domain.LiveMetric
|
||||
|
||||
val, err := s.redisClient.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
// Key does not exist yet, return zero-valued struct
|
||||
return domain.LiveMetric{}, nil
|
||||
} else if err != nil {
|
||||
return domain.LiveMetric{}, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(val), &metric); err != nil {
|
||||
return domain.LiveMetric{}, err
|
||||
}
|
||||
|
||||
return metric, nil
|
||||
}
|
||||
|
||||
// func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) {
|
||||
// var (
|
||||
// payload domain.LiveWalletMetrics
|
||||
// event map[string]interface{}
|
||||
// key = "live_metrics"
|
||||
// )
|
||||
|
||||
// // Try company first
|
||||
// company, companyErr := s.notificationStore.GetCompanyByWalletID(ctx, wallet.ID)
|
||||
// if companyErr == nil {
|
||||
// payload = domain.LiveWalletMetrics{
|
||||
// Timestamp: time.Now(),
|
||||
// CompanyBalances: []domain.CompanyWalletBalance{{
|
||||
// CompanyID: company.ID,
|
||||
// CompanyName: company.Name,
|
||||
// Balance: float64(wallet.Balance),
|
||||
// }},
|
||||
// BranchBalances: []domain.BranchWalletBalance{},
|
||||
// }
|
||||
|
||||
// event = map[string]interface{}{
|
||||
// "type": "LIVE_WALLET_METRICS_UPDATE",
|
||||
// "recipient_id": company.ID,
|
||||
// "recipient_type": "company",
|
||||
// "payload": payload,
|
||||
// }
|
||||
// } else {
|
||||
// // Try branch next
|
||||
// branch, branchErr := s.notificationStore.GetBranchByWalletID(ctx, wallet.ID)
|
||||
// if branchErr == nil {
|
||||
// payload = domain.LiveWalletMetrics{
|
||||
// Timestamp: time.Now(),
|
||||
// CompanyBalances: []domain.CompanyWalletBalance{},
|
||||
// BranchBalances: []domain.BranchWalletBalance{{
|
||||
// BranchID: branch.ID,
|
||||
// BranchName: branch.Name,
|
||||
// CompanyID: branch.CompanyID,
|
||||
// Balance: float64(wallet.Balance),
|
||||
// }},
|
||||
// }
|
||||
|
||||
// event = map[string]interface{}{
|
||||
// "type": "LIVE_WALLET_METRICS_UPDATE",
|
||||
// "recipient_id": branch.ID,
|
||||
// "recipient_type": "branch",
|
||||
// "payload": payload,
|
||||
// }
|
||||
// } else {
|
||||
// // Neither company nor branch matched this wallet
|
||||
// // s.logger.Warn("wallet not linked to any company or branch", "walletID", wallet.ID)
|
||||
// s.mongoLogger.Warn("wallet not linked to any company or branch",
|
||||
// zap.Int64("walletID", wallet.ID),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Save latest metric to Redis
|
||||
// if jsonBytes, err := json.Marshal(payload); err == nil {
|
||||
// s.redisClient.Set(ctx, key, jsonBytes, 0)
|
||||
// } else {
|
||||
// // s.logger.Error("failed to marshal wallet metrics payload", "walletID", wallet.ID, "err", err)
|
||||
// s.mongoLogger.Error("failed to marshal wallet metrics payload",
|
||||
// zap.Int64("walletID", wallet.ID),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// }
|
||||
|
||||
// // Publish via Redis
|
||||
// if jsonEvent, err := json.Marshal(event); err == nil {
|
||||
// s.redisClient.Publish(ctx, key, jsonEvent)
|
||||
// } else {
|
||||
// // s.logger.Error("failed to marshal event payload", "walletID", wallet.ID, "err", err)
|
||||
// s.mongoLogger.Error("failed to marshal event payload",
|
||||
// zap.Int64("walletID", wallet.ID),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// }
|
||||
|
||||
// // Broadcast over WebSocket
|
||||
// s.Hub.Broadcast <- event
|
||||
// }
|
||||
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"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/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||
"go.uber.org/zap"
|
||||
|
|
|
|||
|
|
@ -2,40 +2,36 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/resend/resend-go/v2"
|
||||
"github.com/twilio/twilio-go"
|
||||
twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.OtpProvider) error {
|
||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
|
||||
otpCode := helpers.GenerateOTP()
|
||||
|
||||
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
|
||||
|
||||
switch medium {
|
||||
case domain.OtpMediumSms:
|
||||
|
||||
switch provider {
|
||||
case "twilio":
|
||||
if err := s.SendTwilioSMSOTP(ctx, sentTo, message, provider); err != nil {
|
||||
case domain.TwilioSms:
|
||||
if err := s.messengerSvc.SendTwilioSMS(ctx, sentTo, message); err != nil {
|
||||
return err
|
||||
}
|
||||
case "afromessage":
|
||||
if err := s.SendAfroMessageSMSOTP(ctx, sentTo, message, provider); err != nil {
|
||||
case domain.AfroMessage:
|
||||
if err := s.messengerSvc.SendAfroMessageSMS(ctx, sentTo, message); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid sms provider: %s", provider)
|
||||
}
|
||||
case domain.OtpMediumEmail:
|
||||
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
|
||||
if err := s.messengerSvc.SendEmail(ctx, sentTo, message, "FortuneBets - One Time Password"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -61,73 +57,3 @@ func hashPassword(plaintextPassword string) ([]byte, error) {
|
|||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (s *Service) SendAfroMessageSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
|
||||
apiKey := s.config.AFRO_SMS_API_KEY
|
||||
senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||
hostURL := s.config.ADRO_SMS_HOST_URL
|
||||
endpoint := "/api/send"
|
||||
|
||||
// API endpoint has been updated
|
||||
// TODO: no need for package for the afro message operations (pretty simple stuff)
|
||||
request := afro.GetRequest(apiKey, endpoint, hostURL)
|
||||
request.BaseURL = "https://api.afromessage.com/api/send"
|
||||
|
||||
request.Method = "GET"
|
||||
request.Sender(senderName)
|
||||
request.To(receiverPhone, message)
|
||||
|
||||
response, err := afro.MakeRequestWithContext(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response["acknowledge"] == "success" {
|
||||
return nil
|
||||
} else {
|
||||
fmt.Println(response["response"].(map[string]interface{}))
|
||||
return errors.New("SMS delivery failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendTwilioSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
|
||||
accountSid := s.config.TwilioAccountSid
|
||||
authToken := s.config.TwilioAuthToken
|
||||
senderPhone := s.config.TwilioSenderPhoneNumber
|
||||
|
||||
client := twilio.NewRestClientWithParams(twilio.ClientParams{
|
||||
Username: accountSid,
|
||||
Password: authToken,
|
||||
})
|
||||
|
||||
params := &twilioApi.CreateMessageParams{}
|
||||
params.SetTo(receiverPhone)
|
||||
params.SetFrom(senderPhone)
|
||||
params.SetBody(message)
|
||||
|
||||
_, err := client.Api.CreateMessage(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", "Error sending SMS message: %s" + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error {
|
||||
apiKey := s.config.ResendApiKey
|
||||
client := resend.NewClient(apiKey)
|
||||
formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">"
|
||||
params := &resend.SendEmailRequest{
|
||||
From: formattedSenderEmail,
|
||||
To: []string{receiverEmail},
|
||||
Subject: "FortuneBets - One Time Password",
|
||||
Text: message,
|
||||
}
|
||||
|
||||
_, err := client.Emails.Send(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
|
|||
return s.userStore.DeleteUser(ctx, id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
|
||||
// Get all Users
|
||||
return s.userStore.GetAllUsers(ctx, filter)
|
||||
|
|
@ -58,7 +56,10 @@ func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]do
|
|||
return s.userStore.GetCashiersByBranch(ctx, branchID)
|
||||
}
|
||||
|
||||
func (s *Service) GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error){
|
||||
func (s *Service) GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error) {
|
||||
return s.userStore.GetAdminByCompanyID(ctx, companyID)
|
||||
}
|
||||
func (s *Service) GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error) {
|
||||
return s.userStore.GetAllCashiers(ctx, filter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ type UserStore interface {
|
|||
GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error)
|
||||
GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error)
|
||||
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
|
||||
GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error)
|
||||
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||
UpdateUserCompany(ctx context.Context, id int64, companyID int64) error
|
||||
UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
||||
}
|
||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
|
||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
var err error
|
||||
// check if user exists
|
||||
switch medium {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
|
||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
|
||||
var err error
|
||||
// check if user exists
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -11,19 +12,22 @@ const (
|
|||
)
|
||||
|
||||
type Service struct {
|
||||
userStore UserStore
|
||||
otpStore OtpStore
|
||||
config *config.Config
|
||||
userStore UserStore
|
||||
otpStore OtpStore
|
||||
messengerSvc *messenger.Service
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewService(
|
||||
userStore UserStore,
|
||||
otpStore OtpStore,
|
||||
messengerSvc *messenger.Service,
|
||||
cfg *config.Config,
|
||||
) *Service {
|
||||
return &Service{
|
||||
userStore: userStore,
|
||||
otpStore: otpStore,
|
||||
config: cfg,
|
||||
userStore: userStore,
|
||||
otpStore: otpStore,
|
||||
messengerSvc: messengerSvc,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
|||
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
|
||||
}
|
||||
_, err = s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents),
|
||||
domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
fmt.Sprintf("Deducted %v amount from wallet by system while placing virtual game bet", amountCents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("insufficient balance")
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ func (c *Client) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain
|
|||
return &domain.BetResponse{}, err
|
||||
}
|
||||
|
||||
c.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
c.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
fmt.Sprintf("Deducting %v from wallet for creating Veli Game Bet", req.Amount.Amount),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
)
|
||||
|
||||
type WalletStore interface {
|
||||
// GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||
// GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||
GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||
GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||
CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error)
|
||||
CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error)
|
||||
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ package wallet
|
|||
import (
|
||||
"log/slog"
|
||||
|
||||
<<<<<<< HEAD
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
=======
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"go.uber.org/zap"
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
|
@ -13,17 +19,33 @@ type Service struct {
|
|||
transferStore TransferStore
|
||||
notificationStore notificationservice.NotificationStore
|
||||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
mongoLogger *zap.Logger
|
||||
logger *slog.Logger
|
||||
kafkaProducer *kafka.Producer
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger, kafkaProducer *kafka.Producer) *Service {
|
||||
=======
|
||||
func NewService(
|
||||
walletStore WalletStore,
|
||||
transferStore TransferStore,
|
||||
notificationStore notificationservice.NotificationStore,
|
||||
notificationSvc *notificationservice.Service,
|
||||
userSvc *user.Service,
|
||||
mongoLogger *zap.Logger,
|
||||
logger *slog.Logger,
|
||||
) *Service {
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
return &Service{
|
||||
walletStore: walletStore,
|
||||
transferStore: transferStore,
|
||||
// approvalStore: approvalStore,
|
||||
notificationStore: notificationStore,
|
||||
notificationSvc: notificationSvc,
|
||||
userSvc: userSvc,
|
||||
mongoLogger: mongoLogger,
|
||||
logger: logger,
|
||||
kafkaProducer: kafkaProducer,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,14 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
<<<<<<< HEAD
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
|
||||
=======
|
||||
"go.uber.org/zap"
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -60,6 +65,14 @@ func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wall
|
|||
return s.walletStore.GetWalletsByUser(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||
return s.walletStore.GetCompanyByWalletID(ctx, walletID)
|
||||
}
|
||||
|
||||
func (s *Service) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||
return s.walletStore.GetBranchByWalletID(ctx, walletID)
|
||||
}
|
||||
|
||||
func (s *Service) GetAllCustomerWallet(ctx context.Context) ([]domain.GetCustomerWallet, error) {
|
||||
return s.walletStore.GetAllCustomerWallets(ctx)
|
||||
}
|
||||
|
|
@ -77,11 +90,16 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu
|
|||
return err
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
wallet, err := s.walletStore.GetWalletByID(ctx, id)
|
||||
=======
|
||||
_, err = s.GetWalletByID(ctx, id)
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
go func() {
|
||||
s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{
|
||||
EventType: event.WalletBalanceUpdated,
|
||||
|
|
@ -92,6 +110,9 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu
|
|||
})
|
||||
}()
|
||||
|
||||
=======
|
||||
// go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet)
|
||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +156,7 @@ func (s *Service) AddToWallet(
|
|||
return newTransfer, err
|
||||
}
|
||||
|
||||
func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency, walletType domain.WalletType, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, message string) (domain.Transfer, error) {
|
||||
func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, message string) (domain.Transfer, error) {
|
||||
wallet, err := s.GetWalletByID(ctx, id)
|
||||
if err != nil {
|
||||
return domain.Transfer{}, err
|
||||
|
|
@ -143,12 +164,32 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.
|
|||
|
||||
if wallet.Balance < amount {
|
||||
// Send Wallet low to admin
|
||||
if walletType == domain.CompanyWalletType || walletType == domain.BranchWalletType {
|
||||
s.SendAdminWalletLowNotification(ctx, wallet, amount)
|
||||
if wallet.Type == domain.CompanyWalletType || wallet.Type == domain.BranchWalletType {
|
||||
s.SendAdminWalletInsufficientNotification(ctx, wallet, amount)
|
||||
} else {
|
||||
s.SendCustomerWalletInsufficientNotification(ctx, wallet, amount)
|
||||
}
|
||||
return domain.Transfer{}, ErrBalanceInsufficient
|
||||
}
|
||||
|
||||
if wallet.Type == domain.BranchWalletType || wallet.Type == domain.CompanyWalletType {
|
||||
var thresholds []float32
|
||||
|
||||
if wallet.Type == domain.CompanyWalletType {
|
||||
thresholds = []float32{100000, 50000, 25000, 10000, 5000, 3000, 1000, 500}
|
||||
} else {
|
||||
thresholds = []float32{5000, 3000, 1000, 500}
|
||||
}
|
||||
|
||||
balance := wallet.Balance.Float32()
|
||||
for _, threshold := range thresholds {
|
||||
if balance < threshold {
|
||||
s.SendAdminWalletLowNotification(ctx, wallet)
|
||||
break // only send once per check
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = s.walletStore.UpdateBalance(ctx, id, wallet.Balance-amount)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -223,46 +264,208 @@ func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive boo
|
|||
return s.walletStore.UpdateWalletActive(ctx, id, isActive)
|
||||
}
|
||||
|
||||
func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWallet domain.Wallet, amount domain.Currency) error {
|
||||
func (s *Service) GetAdminNotificationRecipients(ctx context.Context, walletID int64, walletType domain.WalletType) ([]int64, error) {
|
||||
var recipients []int64
|
||||
|
||||
if walletType == domain.BranchWalletType {
|
||||
branch, err := s.GetBranchByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recipients = append(recipients, branch.BranchManagerID)
|
||||
|
||||
cashiers, err := s.userSvc.GetCashiersByBranch(ctx, branch.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cashier := range cashiers {
|
||||
recipients = append(recipients, cashier.ID)
|
||||
}
|
||||
|
||||
admin, err := s.userSvc.GetAdminByCompanyID(ctx, branch.CompanyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recipients = append(recipients, admin.ID)
|
||||
|
||||
} else if walletType == domain.CompanyWalletType {
|
||||
company, err := s.GetCompanyByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recipients = append(recipients, company.AdminID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid wallet type")
|
||||
}
|
||||
|
||||
users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
|
||||
Role: string(domain.RoleSuperAdmin),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
recipients = append(recipients, user.ID)
|
||||
}
|
||||
|
||||
return recipients, nil
|
||||
}
|
||||
|
||||
func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWallet domain.Wallet) error {
|
||||
// Send notification to admin team
|
||||
adminNotification := &domain.Notification{
|
||||
RecipientID: adminWallet.UserID,
|
||||
Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT,
|
||||
Level: domain.NotificationLevelError,
|
||||
Level: domain.NotificationLevelWarning,
|
||||
Reciever: domain.NotificationRecieverSideAdmin,
|
||||
DeliveryChannel: domain.DeliveryChannelEmail, // Or any preferred admin channel
|
||||
DeliveryChannel: domain.DeliveryChannelInApp, // Or any preferred admin channel
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "CREDIT WARNING: System Running Out of Funds",
|
||||
Message: fmt.Sprintf(
|
||||
"Wallet ID %d has insufficient balance for transfer. Current balance: %.2f, Attempted transfer: %.2f",
|
||||
"Wallet ID %d is running low. Current balance: %.2f",
|
||||
adminWallet.ID,
|
||||
adminWallet.Balance.Float32(),
|
||||
amount.Float32(),
|
||||
),
|
||||
},
|
||||
Priority: 1, // High priority for admin alerts
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"wallet_id": %d,
|
||||
"balance": %d,
|
||||
"required_amount": %d,
|
||||
"notification_type": "admin_alert"
|
||||
}`, adminWallet.ID, adminWallet.Balance, amount),
|
||||
}`, adminWallet.ID, adminWallet.Balance),
|
||||
}
|
||||
|
||||
// Get admin recipients and send to all
|
||||
adminRecipients, err := s.notificationStore.ListRecipientIDs(ctx, domain.NotificationRecieverSideAdmin)
|
||||
adminRecipients, err := s.GetAdminNotificationRecipients(ctx, adminWallet.ID, adminWallet.Type)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to get admin recipients", "error", err)
|
||||
s.mongoLogger.Error("failed to get admin recipients",
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
} else {
|
||||
for _, adminID := range adminRecipients {
|
||||
adminNotification.RecipientID = adminID
|
||||
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
||||
s.logger.Error("failed to send admin notification",
|
||||
"admin_id", adminID,
|
||||
"error", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, adminID := range adminRecipients {
|
||||
adminNotification.RecipientID = adminID
|
||||
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send admin notification",
|
||||
zap.Int64("admin_id", adminID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
|
||||
adminNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
|
||||
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send email admin notification",
|
||||
zap.Int64("admin_id", adminID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendAdminWalletInsufficientNotification(ctx context.Context, adminWallet domain.Wallet, amount domain.Currency) error {
|
||||
|
||||
// Send notification to admin team
|
||||
adminNotification := &domain.Notification{
|
||||
RecipientID: adminWallet.UserID,
|
||||
Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT,
|
||||
Level: domain.NotificationLevelError,
|
||||
Reciever: domain.NotificationRecieverSideAdmin,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp, // Or any preferred admin channel
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "CREDIT Error: Admin Wallet insufficient to process customer request",
|
||||
Message: fmt.Sprintf(
|
||||
"Wallet ID %d. Transaction Amount %.2f. Current balance: %.2f",
|
||||
adminWallet.ID,
|
||||
amount.Float32(),
|
||||
adminWallet.Balance.Float32(),
|
||||
),
|
||||
},
|
||||
Priority: 1, // High priority for admin alerts
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"wallet_id": %d,
|
||||
"balance": %d,
|
||||
"transaction amount": %.2f,
|
||||
"notification_type": "admin_alert"
|
||||
}`, adminWallet.ID, adminWallet.Balance, amount.Float32()),
|
||||
}
|
||||
|
||||
// Get admin recipients and send to all
|
||||
|
||||
recipients, err := s.GetAdminNotificationRecipients(ctx, adminWallet.ID, adminWallet.Type)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get admin recipients",
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
for _, adminID := range recipients {
|
||||
adminNotification.RecipientID = adminID
|
||||
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send admin notification",
|
||||
zap.Int64("admin_id", adminID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
}
|
||||
adminNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
|
||||
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send email admin notification",
|
||||
zap.Int64("admin_id", adminID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendCustomerWalletInsufficientNotification(ctx context.Context, customerWallet domain.Wallet, amount domain.Currency) error {
|
||||
// Send notification to admin team
|
||||
customerNotification := &domain.Notification{
|
||||
RecipientID: customerWallet.UserID,
|
||||
Type: domain.NOTIFICATION_TYPE_WALLET,
|
||||
Level: domain.NotificationLevelError,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp, // Or any preferred admin channel
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "CREDIT Error: Wallet insufficient",
|
||||
Message: fmt.Sprintf(
|
||||
"Wallet ID %d. Transaction Amount %.2f. Current balance: %.2f",
|
||||
customerWallet.ID,
|
||||
amount.Float32(),
|
||||
customerWallet.Balance.Float32(),
|
||||
),
|
||||
},
|
||||
Priority: 1, // High priority for admin alerts
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"wallet_id": %d,
|
||||
"balance": %d,
|
||||
"transaction amount": %.2f,
|
||||
"notification_type": "admin_alert"
|
||||
}`, customerWallet.ID, customerWallet.Balance, amount.Float32()),
|
||||
}
|
||||
|
||||
if err := s.notificationStore.SendNotification(ctx, customerNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to create customer notification",
|
||||
zap.Int64("customer_id", customerWallet.UserID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
"go.uber.org/zap"
|
||||
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
|
|
|
|||
|
|
@ -24,22 +24,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() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
|
|
@ -14,7 +15,7 @@ import (
|
|||
|
||||
// loginCustomerReq represents the request body for the LoginCustomer endpoint.
|
||||
type loginCustomerReq struct {
|
||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
||||
Email string `json:"email" validate:"required_without=PhoneNumber" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
Password string `json:"password" validate:"required" example:"password123"`
|
||||
}
|
||||
|
|
@ -37,7 +38,7 @@ type loginCustomerRes struct {
|
|||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/auth/login [post]
|
||||
// @Router /api/v1/auth/customer-login [post]
|
||||
func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||
var req loginCustomerReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
|
|
@ -59,7 +60,6 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
||||
if err != nil {
|
||||
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||
|
|
@ -89,6 +89,133 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
if successRes.Role != domain.RoleCustomer {
|
||||
h.mongoLoggerSvc.Info("Login attempt: customer login of other role",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only customers are allowed to login ")
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("user_id", successRes.UserId),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
Role: string(successRes.Role),
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("Login successful",
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Int64("user_id", successRes.UserId),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||
}
|
||||
|
||||
// loginAdminReq represents the request body for the LoginAdmin endpoint.
|
||||
type loginAdminReq struct {
|
||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
Password string `json:"password" validate:"required" example:"password123"`
|
||||
}
|
||||
|
||||
// loginAdminRes represents the response body for the LoginAdmin endpoint.
|
||||
type loginAdminRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// LoginAdmin godoc
|
||||
// @Summary Login customer
|
||||
// @Description Login customer
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param login body loginAdminReq true "Login admin"
|
||||
// @Success 200 {object} loginAdminRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/auth/admin-login [post]
|
||||
func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
||||
var req loginAdminReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse LoginAdmin request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
var errMsg string
|
||||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
|
||||
case errors.Is(err, authentication.ErrUserSuspended):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
|
||||
default:
|
||||
h.mongoLoggerSvc.Error("Login failed",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
}
|
||||
|
||||
if successRes.Role == domain.RoleCustomer {
|
||||
h.mongoLoggerSvc.Warn("Login attempt: admin login of customer",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
|||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("role", string(role)),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error())
|
||||
|
|
@ -97,6 +98,15 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, "failed to get bet with fast code:"+err.Error())
|
||||
}
|
||||
|
||||
if bet.UserID == userID {
|
||||
h.mongoLoggerSvc.Info("User cannot refer himself",
|
||||
zap.Int64("bet_id", bet.ID),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "User cannot use his own referral code")
|
||||
}
|
||||
outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID",
|
||||
|
|
@ -190,7 +200,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
|
|||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Error("PlaceBet failed",
|
||||
|
|
@ -202,7 +212,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
|
|||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet")
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
|
@ -490,6 +500,42 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
// GetBetByFastCode godoc
|
||||
// @Summary Gets bet by fast_code
|
||||
// @Description Gets a single bet by fast_code
|
||||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param fast_code path int true "Bet ID"
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/sport/bet/fastcode/{fast_code} [get]
|
||||
func (h *Handler) GetBetByFastCode(c *fiber.Ctx) error {
|
||||
fastCode := c.Params("fast_code")
|
||||
|
||||
bet, err := h.betSvc.GetBetByFastCode(c.Context(), fastCode)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to get bet by fast code",
|
||||
zap.String("fast_code", fastCode),
|
||||
zap.Int("status_code", fiber.StatusNotFound),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusNotFound, "Failed to find bet by fast code")
|
||||
}
|
||||
|
||||
res := domain.ConvertBet(bet)
|
||||
|
||||
// h.mongoLoggerSvc.Info("Bet retrieved successfully",
|
||||
// zap.Int64("betID", id),
|
||||
// zap.Int("status_code", fiber.StatusOK),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
type UpdateCashOutReq struct {
|
||||
CashedOut bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,12 +99,13 @@ func (h *Handler) CreateBranch(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
branch, err := h.branchSvc.CreateBranch(c.Context(), domain.CreateBranch{
|
||||
Name: req.Name,
|
||||
Location: req.Location,
|
||||
WalletID: newWallet.ID,
|
||||
BranchManagerID: req.BranchManagerID,
|
||||
CompanyID: checkedCompanyID,
|
||||
IsSelfOwned: IsSelfOwned,
|
||||
Name: req.Name,
|
||||
Location: req.Location,
|
||||
WalletID: newWallet.ID,
|
||||
BranchManagerID: req.BranchManagerID,
|
||||
CompanyID: checkedCompanyID,
|
||||
IsSelfOwned: IsSelfOwned,
|
||||
ProfitPercentage: req.ProfitPercentage,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -619,6 +620,38 @@ func (h *Handler) GetBranchOperations(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Branch Operations retrieved successfully", result, nil)
|
||||
}
|
||||
|
||||
// GetAllBranchLocations godoc
|
||||
// @Summary Gets all branch locations
|
||||
// @Description Gets all branch locations
|
||||
// @Tags branch
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} domain.BranchLocation
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/branchLocation [get]
|
||||
func (h *Handler) GetAllBranchLocations(c *fiber.Ctx) error {
|
||||
|
||||
searchQuery := c.Query("query")
|
||||
searchString := domain.ValidString{
|
||||
Value: searchQuery,
|
||||
Valid: searchQuery != "",
|
||||
}
|
||||
|
||||
locations, err := h.branchSvc.GetAllBranchLocations(c.Context(), searchString)
|
||||
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to get branch locations",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Branch Location successfully fetched", locations, nil)
|
||||
}
|
||||
|
||||
// GetBranchCashiers godoc
|
||||
// @Summary Gets branch cashiers
|
||||
// @Description Gets branch cashiers
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
|
|||
user, err := h.userSvc.GetUserByID(c.Context(), req.AdminID)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Error fetching user",
|
||||
zap.Int("admin_id", int(req.AdminID)),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -76,9 +77,10 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
company, err := h.companySvc.CreateCompany(c.Context(), domain.CreateCompany{
|
||||
Name: req.Name,
|
||||
AdminID: user.ID,
|
||||
WalletID: newWallet.ID,
|
||||
Name: req.Name,
|
||||
AdminID: user.ID,
|
||||
WalletID: newWallet.ID,
|
||||
DeductedPercentage: req.DeductedPercentage,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery := c.Query("query")
|
||||
searchString := domain.ValidString{
|
||||
Value: searchQuery,
|
||||
Valid: searchQuery != "",
|
||||
}
|
||||
|
||||
firstStartTimeQuery := c.Query("first_start_time")
|
||||
var firstStartTime domain.ValidTime
|
||||
if firstStartTimeQuery != "" {
|
||||
|
|
@ -98,7 +105,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
if lastStartTimeQuery != "" {
|
||||
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("invalid start_time format",
|
||||
h.mongoLoggerSvc.Info("invalid last_start_time format",
|
||||
zap.String("last_start_time", lastStartTimeQuery),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
|
|
@ -118,12 +125,12 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
Valid: countryCodeQuery != "",
|
||||
}
|
||||
|
||||
flaggedQuery := c.Query("flagged")
|
||||
var flagged domain.ValidBool
|
||||
if flaggedQuery != "" {
|
||||
flaggedParsed, err := strconv.ParseBool(flaggedQuery)
|
||||
isFeaturedQuery := c.Query("is_featured")
|
||||
var isFeatured domain.ValidBool
|
||||
if isFeaturedQuery != "" {
|
||||
isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to parse flagged",
|
||||
h.mongoLoggerSvc.Error("Failed to parse isFeatured",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -131,8 +138,8 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
|
||||
}
|
||||
|
||||
flagged = domain.ValidBool{
|
||||
Value: flaggedParsed,
|
||||
isFeatured = domain.ValidBool{
|
||||
Value: isFeaturedParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -141,12 +148,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
c.Context(), domain.EventFilter{
|
||||
SportID: sportID,
|
||||
LeagueID: leagueID,
|
||||
Query: searchString,
|
||||
FirstStartTime: firstStartTime,
|
||||
LastStartTime: lastStartTime,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
CountryCode: countryCode,
|
||||
Flagged: flagged,
|
||||
Featured: isFeatured,
|
||||
})
|
||||
|
||||
// fmt.Printf("League ID: %v", leagueID)
|
||||
|
|
@ -299,13 +307,13 @@ func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error {
|
|||
|
||||
}
|
||||
|
||||
type UpdateEventFlaggedReq struct {
|
||||
Flagged bool `json:"flagged" example:"true"`
|
||||
type UpdateEventFeaturedReq struct {
|
||||
Featured bool `json:"is_featured" example:"true"`
|
||||
}
|
||||
|
||||
// UpdateEventFlagged godoc
|
||||
// @Summary update the event flagged
|
||||
// @Description Update the event flagged
|
||||
// UpdateEventFeatured godoc
|
||||
// @Summary update the event featured
|
||||
// @Description Update the event featured
|
||||
// @Tags event
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
|
@ -314,10 +322,10 @@ type UpdateEventFlaggedReq struct {
|
|||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/events/{id}/flag [put]
|
||||
func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
|
||||
func (h *Handler) UpdateEventFeatured(c *fiber.Ctx) error {
|
||||
eventID := c.Params("id")
|
||||
|
||||
var req UpdateEventFlaggedReq
|
||||
var req UpdateEventFeaturedReq
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse user id",
|
||||
|
|
@ -335,17 +343,17 @@ func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
|
|||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
h.mongoLoggerSvc.Error("Failed to update event flagged",
|
||||
h.mongoLoggerSvc.Error("Failed to update event featured",
|
||||
zap.Any("request", req),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
err := h.eventSvc.UpdateFlagged(c.Context(), eventID, req.Flagged)
|
||||
err := h.eventSvc.UpdateFeatured(c.Context(), eventID, req.Featured)
|
||||
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to update event flagged",
|
||||
h.mongoLoggerSvc.Error("Failed to update event featured",
|
||||
zap.String("eventID", eventID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
|
||||
issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
|
|
|
|||
|
|
@ -115,7 +115,23 @@ func (h *Handler) GetAllIssues(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get all issues:"+err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(issues)
|
||||
results := make([]domain.ReportedIssue, len(issues))
|
||||
for i, issue := range issues {
|
||||
results[i] = domain.ReportedIssue{
|
||||
ID: issue.ID,
|
||||
UserID: issue.UserID,
|
||||
UserRole: domain.Role(issue.UserRole),
|
||||
Subject: issue.Subject,
|
||||
Description: issue.Description,
|
||||
IssueType: domain.ReportedIssueType(issue.IssueType),
|
||||
Status: domain.ReportedIssueStatus(issue.Status),
|
||||
// Metadata: issue.Metadata,
|
||||
CreatedAt: issue.CreatedAt.Time,
|
||||
UpdatedAt: issue.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(results)
|
||||
}
|
||||
|
||||
// UpdateIssueStatus godoc
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
|
|||
|
||||
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to update league active",
|
||||
zap.Int64("userID", int64(leagueId)),
|
||||
zap.Int64("leagueID", int64(leagueId)),
|
||||
zap.Bool("is_active", req.IsActive),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
|
|
@ -149,6 +149,14 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error())
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("League Active has been successfully updated",
|
||||
zap.Int64("userID", int64(leagueId)),
|
||||
zap.Int64("leagueID", int64(leagueId)),
|
||||
zap.Bool("is_active", req.IsActive),
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil)
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +214,10 @@ func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error {
|
|||
}
|
||||
err = h.leagueSvc.UpdateLeague(c.Context(), domain.UpdateLeague{
|
||||
ID: int64(leagueId),
|
||||
IsFeatured: domain.ValidBool{
|
||||
Value: req.IsFeatured,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to update league",
|
||||
|
|
@ -216,6 +228,12 @@ func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error {
|
|||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error())
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("League Featured has been successfully updated",
|
||||
zap.Int64("userID", int64(leagueId)),
|
||||
zap.Int64("leagueID", int64(leagueId)),
|
||||
zap.Bool("is_featured", req.IsFeatured),
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func GetLogsHandler(appCtx context.Context) fiber.Handler {
|
|||
}
|
||||
defer cursor.Close(appCtx)
|
||||
|
||||
var logs []domain.LogEntry
|
||||
var logs []domain.LogEntry = make([]domain.LogEntry, 0)
|
||||
if err := cursor.All(appCtx, &logs); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Cursor decoding error: "+err.Error())
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ func GetLogsHandler(appCtx context.Context) fiber.Handler {
|
|||
// Prepare response
|
||||
response := domain.LogResponse{
|
||||
Message: "Logs fetched successfully",
|
||||
Data: logs,
|
||||
Data: logs,
|
||||
Pagination: domain.Pagination{
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
|
|||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
} else {
|
||||
h.mongoLoggerSvc.Warn("Unexpected WebSocket closure",
|
||||
h.mongoLoggerSvc.Info("Unexpected WebSocket closure",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
|
|
|
|||
|
|
@ -35,11 +35,13 @@ import (
|
|||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/reports/dashboard [get]
|
||||
func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
|
||||
role := c.Locals("role").(domain.Role)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Parse query parameters
|
||||
filter, err := parseReportFilter(c)
|
||||
filter, err := parseReportFilter(c, role)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid filter parameters",
|
||||
|
|
@ -70,24 +72,30 @@ func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
// parseReportFilter parses query parameters into ReportFilter
|
||||
func parseReportFilter(c *fiber.Ctx) (domain.ReportFilter, error) {
|
||||
func parseReportFilter(c *fiber.Ctx, role domain.Role) (domain.ReportFilter, error) {
|
||||
var filter domain.ReportFilter
|
||||
var err error
|
||||
|
||||
if c.Query("company_id") != "" {
|
||||
if c.Query("company_id") != "" && role == domain.RoleSuperAdmin {
|
||||
|
||||
companyID, err := strconv.ParseInt(c.Query("company_id"), 10, 64)
|
||||
if err != nil {
|
||||
return domain.ReportFilter{}, fmt.Errorf("invalid company_id: %w", err)
|
||||
}
|
||||
filter.CompanyID = domain.ValidInt64{Value: companyID, Valid: true}
|
||||
} else {
|
||||
filter.CompanyID = c.Locals("company_id").(domain.ValidInt64)
|
||||
|
||||
}
|
||||
|
||||
if c.Query("branch_id") != "" {
|
||||
if c.Query("branch_id") != "" && role == domain.RoleSuperAdmin {
|
||||
branchID, err := strconv.ParseInt(c.Query("branch_id"), 10, 64)
|
||||
if err != nil {
|
||||
return domain.ReportFilter{}, fmt.Errorf("invalid branch_id: %w", err)
|
||||
}
|
||||
filter.BranchID = domain.ValidInt64{Value: branchID, Valid: true}
|
||||
} else {
|
||||
filter.BranchID = c.Locals("branch_id").(domain.ValidInt64)
|
||||
}
|
||||
|
||||
if c.Query("user_id") != "" {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,89 @@ func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Shop bet fetched successfully", res, nil)
|
||||
}
|
||||
|
||||
// GetAllShopBets godoc
|
||||
// @Summary Gets all shop bets
|
||||
// @Description Gets all the shop bets
|
||||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} domain.ShopBetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/shop/bet [get]
|
||||
func (h *Handler) GetAllShopBets(c *fiber.Ctx) error {
|
||||
// role := c.Locals("role").(domain.Role)
|
||||
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
branchID := c.Locals("branch_id").(domain.ValidInt64)
|
||||
|
||||
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.mongoLoggerSvc.Info("invalid created_before format",
|
||||
zap.String("time", createdBeforeQuery),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
|
||||
}
|
||||
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.mongoLoggerSvc.Info("invalid created_after format",
|
||||
zap.String("created_after", createdAfterQuery),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
|
||||
}
|
||||
createdAfter = domain.ValidTime{
|
||||
Value: createdAfterParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
bets, err := h.transactionSvc.GetAllShopBet(c.Context(), domain.ShopBetFilter{
|
||||
Query: searchString,
|
||||
CreatedBefore: createdBefore,
|
||||
CreatedAfter: createdAfter,
|
||||
CompanyID: companyID,
|
||||
BranchID: branchID,
|
||||
})
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to get all bets",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets፡"+err.Error())
|
||||
}
|
||||
|
||||
res := make([]domain.ShopBetRes, len(bets))
|
||||
for i, bet := range bets {
|
||||
res[i] = domain.ConvertShopBetDetail(bet)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
// CashoutBet godoc
|
||||
// @Summary Cashout bet at branch
|
||||
// @Description Cashout bet at branch
|
||||
|
|
|
|||
|
|
@ -120,7 +120,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, "twilio"); err != nil {
|
||||
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, domain.AfroMessage); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to send register code",
|
||||
zap.String("Medium", string(medium)),
|
||||
zap.String("Send To", string(sentTo)),
|
||||
|
|
@ -248,7 +248,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|||
// TODO: Remove later
|
||||
_, err = h.walletSvc.AddToWallet(
|
||||
c.Context(), newWallet.RegularID, domain.ToCurrency(10000.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||
"Added 100.0 to wallet only as test for deployment")
|
||||
"Added 10000.0 to wallet only as test for deployment")
|
||||
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to update wallet for user",
|
||||
|
|
@ -318,7 +318,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, "twilio"); err != nil {
|
||||
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, domain.AfroMessage); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to send reset code",
|
||||
zap.String("medium", string(medium)),
|
||||
zap.String("sentTo", string(sentTo)),
|
||||
|
|
@ -417,20 +417,121 @@ type UserProfileRes struct {
|
|||
LastLogin time.Time `json:"last_login"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
ReferralCode string `json:"referral_code"`
|
||||
}
|
||||
|
||||
// UserProfile godoc
|
||||
type CustomerProfileRes struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Role domain.Role `json:"role"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
ReferralCode string `json:"referral_code"`
|
||||
}
|
||||
|
||||
// CustomerProfile godoc
|
||||
// @Summary Get user profile
|
||||
// @Description Get user profile
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} UserProfileRes
|
||||
// @Success 200 {object} CustomerProfileRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Security Bearer
|
||||
// @Router /api/v1/user/profile [get]
|
||||
func (h *Handler) UserProfile(c *fiber.Ctx) error {
|
||||
// @Router /api/v1/user/customer-profile [get]
|
||||
func (h *Handler) CustomerProfile(c *fiber.Ctx) error {
|
||||
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.mongoLoggerSvc.Error("Invalid user ID in context",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to get user profile",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error())
|
||||
}
|
||||
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
if err != nil {
|
||||
if err != authentication.ErrRefreshTokenNotFound {
|
||||
h.mongoLoggerSvc.Error("Failed to get user last login",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
|
||||
}
|
||||
|
||||
lastLogin = &user.CreatedAt
|
||||
}
|
||||
res := CustomerProfileRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
type AdminProfileRes struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Role domain.Role `json:"role"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
}
|
||||
|
||||
// AdminProfile godoc
|
||||
// @Summary Get user profile
|
||||
// @Description Get user profile
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} AdminProfileRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Security Bearer
|
||||
// @Router /api/v1/user/admin-profile [get]
|
||||
func (h *Handler) AdminProfile(c *fiber.Ctx) error {
|
||||
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
|
|
|
|||
|
|
@ -56,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.0dev10",
|
||||
"version": "1.0dev11",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -67,12 +67,13 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"message": "FortuneBet API V1 pre-alpha",
|
||||
"version": "1.0dev10",
|
||||
"version": "1.0dev11",
|
||||
})
|
||||
})
|
||||
|
||||
// Auth Routes
|
||||
groupV1.Post("/auth/login", h.LoginCustomer)
|
||||
groupV1.Post("/auth/customer-login", h.LoginCustomer)
|
||||
groupV1.Post("/auth/admin-login", h.LoginAdmin)
|
||||
groupV1.Post("/auth/refresh", h.RefreshToken)
|
||||
groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
|
||||
groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
||||
|
|
@ -113,7 +114,8 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Post("/user/register", h.RegisterUser)
|
||||
groupV1.Post("/user/sendRegisterCode", h.SendRegisterCode)
|
||||
groupV1.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
|
||||
groupV1.Get("/user/profile", a.authMiddleware, h.UserProfile)
|
||||
groupV1.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile)
|
||||
groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
|
||||
groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
|
||||
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
|
||||
groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
|
||||
|
|
@ -162,7 +164,7 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/events/:id", h.GetUpcomingEventByID)
|
||||
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
|
||||
groupV1.Get("/top-leagues", h.GetTopLeagues)
|
||||
groupV1.Get("/events/:id/flag", h.UpdateEventFlagged)
|
||||
groupV1.Put("/events/:id/featured", h.UpdateEventFeatured)
|
||||
|
||||
// Leagues
|
||||
groupV1.Get("/leagues", h.GetAllLeagues)
|
||||
|
|
@ -180,9 +182,11 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
|
||||
groupV1.Put("/branch/:id/set-inactive", a.authMiddleware, h.UpdateBranchStatus)
|
||||
groupV1.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch)
|
||||
|
||||
groupV1.Get("/search/branch", a.authMiddleware, h.SearchBranch)
|
||||
// /branch/search
|
||||
// branch/wallet
|
||||
|
||||
groupV1.Get("/branchLocation", a.authMiddleware, h.GetAllBranchLocations)
|
||||
|
||||
groupV1.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers)
|
||||
groupV1.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier)
|
||||
|
||||
|
|
@ -212,6 +216,7 @@ func (a *App) initAppRoutes() {
|
|||
// Bet Routes
|
||||
groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet)
|
||||
groupV1.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
|
||||
groupV1.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode)
|
||||
groupV1.Get("/sport/bet", a.authMiddleware, h.GetAllBet)
|
||||
groupV1.Get("/sport/bet/:id", h.GetBetByID)
|
||||
groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)
|
||||
|
|
@ -245,7 +250,7 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/currencies/convert", h.ConvertCurrency)
|
||||
|
||||
//Report Routes
|
||||
groupV1.Get("/reports/dashboard", h.GetDashboardReport)
|
||||
groupV1.Get("/reports/dashboard", a.authMiddleware, a.OnlyAdminAndAbove, h.GetDashboardReport)
|
||||
groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile)
|
||||
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
|
||||
|
||||
|
|
@ -269,6 +274,7 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
// Transactions /shop/transactions
|
||||
groupV1.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet)
|
||||
groupV1.Get("/shop/bet", a.authMiddleware, a.CompanyOnly, h.GetAllShopBets)
|
||||
groupV1.Get("/shop/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByBetID)
|
||||
groupV1.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
|
||||
groupV1.Post("/shop/bet/:id/generate", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user