fix: version on route, better logs, error display, cleaning up code

This commit is contained in:
Samuel Tariku 2025-07-13 01:23:08 +03:00
parent 5821618d73
commit c83130394b
60 changed files with 17672 additions and 12619 deletions

View File

@ -123,7 +123,7 @@ func main() {
companySvc := company.NewService(store) companySvc := company.NewService(store)
leagueSvc := league.New(store) leagueSvc := league.New(store)
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc) ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *settingSvc, notificationSvc, logger, domain.MongoDBLogger) betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, notificationSvc, logger, domain.MongoDBLogger)
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc) resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
bonusSvc := bonus.NewService(store) bonusSvc := bonus.NewService(store)
referalRepo := repository.NewReferralRepository(store) referalRepo := repository.NewReferralRepository(store)

259
db/data/seed_data.sql Normal file
View File

@ -0,0 +1,259 @@
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended_at,
suspended
)
VALUES (
1,
'John',
'Doe',
'john.doe@example.com',
NULL,
crypt('password123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE
);
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
1,
10000,
TRUE,
TRUE,
TRUE,
1,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
2,
10000,
FALSE,
TRUE,
TRUE,
1,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO customer_wallets (
id,
customer_id,
regular_wallet_id,
static_wallet_id
)
VALUES (1, 1, 1, 2);
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended_at,
suspended,
company_id
)
VALUES (
2,
'Test',
'Admin',
'test.admin@gmail.com',
'0988554466',
crypt('password123', gen_salt('bf'))::bytea,
'admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE,
1
);
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended_at,
suspended
)
VALUES (
3,
'Samuel',
'Tariku',
'cybersamt@gmail.com',
'0911111111',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE
);
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended_at,
suspended
)
VALUES (
4,
'Kirubel',
'Kibru',
'kirubeljkl679 @gmail.com',
'0911554486',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
FALSE
);
INSERT INTO supported_operations (id, name, description)
VALUES (1, 'SportBook', 'Sportbook operations'),
(2, 'Virtual', 'Virtual operations');
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
3,
10000,
TRUE,
TRUE,
TRUE,
2,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO companies (
id,
name,
admin_id,
wallet_id,
deducted_percentage
)
values (
1,
'Test Company',
2,
3,
0.1
);
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
4,
10000,
TRUE,
TRUE,
TRUE,
2,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO branches (
id,
name,
location,
wallet_id,
branch_manager_id,
company_id,
is_self_owned,
created_at,
updated_at
)
values (
1,
'Test Branch',
'Addis Ababa',
4,
2,
1,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
COMMIT;

View File

@ -206,14 +206,16 @@ CREATE TABLE IF NOT EXISTS shop_deposits (
CREATE TABLE IF NOT EXISTS branches ( CREATE TABLE IF NOT EXISTS branches (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
location VARCHAR(255) NOT NULL, location TEXT NOT NULL,
profit_percent REAL NOt NULL,
is_active BOOLEAN NOT NULL DEFAULT false, is_active BOOLEAN NOT NULL DEFAULT false,
wallet_id BIGINT NOT NULL, wallet_id BIGINT NOT NULL,
branch_manager_id BIGINT NOT NULL, branch_manager_id BIGINT NOT NULL,
company_id BIGINT NOT NULL, company_id BIGINT NOT NULL,
is_self_owned BOOLEAN NOT NULL DEFAULT false, is_self_owned BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(wallet_id)
); );
CREATE TABLE IF NOT EXISTS branch_operations ( CREATE TABLE IF NOT EXISTS branch_operations (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -228,6 +230,10 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
branch_id BIGINT NOT NULL, branch_id BIGINT NOT NULL,
UNIQUE(user_id, branch_id) UNIQUE(user_id, branch_id)
); );
CREATE TABLE IF NOT EXISTS branch_locations (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE events ( CREATE TABLE events (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
sport_id INT, sport_id INT,
@ -279,6 +285,8 @@ CREATE TABLE companies (
name TEXT NOT NULL, name TEXT NOT NULL,
admin_id BIGINT NOT NULL, admin_id BIGINT NOT NULL,
wallet_id BIGINT NOT NULL, wallet_id BIGINT NOT NULL,
deducted_percentage REAL NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
@ -314,7 +322,7 @@ CREATE TABLE bonus (
CREATE VIEW companies_details AS CREATE VIEW companies_details AS
SELECT companies.*, SELECT companies.*,
wallets.balance, wallets.balance,
wallets.is_active, wallets.is_active as wallet_is_active,
users.first_name AS admin_first_name, users.first_name AS admin_first_name,
users.last_name AS admin_last_name, users.last_name AS admin_last_name,
users.phone_number AS admin_phone_number users.phone_number AS admin_phone_number
@ -458,7 +466,8 @@ ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) RE
ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id); ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id);
ALTER TABLE branches ALTER TABLE branches
ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id),
ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id); ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id),
ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations(key);
ALTER TABLE branch_operations ALTER TABLE branch_operations
ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE,
ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;

View File

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

View File

@ -0,0 +1,76 @@
-- Locations Initial Data
INSERT INTO branch_locations (key, value)
VALUES ('addis_ababa', 'Addis Ababa'),
('dire_dawa', 'Dire Dawa'),
('mekelle', 'Mekelle'),
('adama', 'Adama'),
('awassa', 'Awassa'),
('bahir_dar', 'Bahir Dar'),
('gonder', 'Gonder'),
('dessie', 'Dessie'),
('jimma', 'Jimma'),
('jijiga', 'Jijiga'),
('shashamane', 'Shashamane'),
('bishoftu', 'Bishoftu'),
('sodo', 'Sodo'),
('arba_minch', 'Arba Minch'),
('hosaena', 'Hosaena'),
('harar', 'Harar'),
('dilla', 'Dilla'),
('nekemte', 'Nekemte'),
('debre_birhan', 'Debre Birhan'),
('asella', 'Asella'),
('debre_markos', 'Debre Markos'),
('kombolcha', 'Kombolcha'),
('debre_tabor', 'Debre Tabor'),
('adigrat', 'Adigrat'),
('areka', 'Areka'),
('weldiya', 'Weldiya'),
('sebeta', 'Sebeta'),
('burayu', 'Burayu'),
('shire', 'Shire'),
('ambo', 'Ambo'),
('arsi_negele', 'Arsi Negele'),
('aksum', 'Aksum'),
('gambela', 'Gambela'),
('bale_robe', 'Bale Robe'),
('butajira', 'Butajira'),
('batu', 'Batu'),
('boditi', 'Boditi'),
('adwa', 'Adwa'),
('yirgalem', 'Yirgalem'),
('waliso', 'Waliso'),
('welkite', 'Welkite'),
('gode', 'Gode'),
('meki', 'Meki'),
('negele_borana', 'Negele Borana'),
('alaba_kulito', 'Alaba Kulito'),
('alamata 14,', 'Alamata 14,'),
('030', '030'),
('chiro', 'Chiro'),
('tepi', 'Tepi'),
('durame', 'Durame'),
('goba', 'Goba'),
('assosa', 'Assosa'),
('gimbi', 'Gimbi'),
('wukro', 'Wukro'),
('haramaya', 'Haramaya'),
('mizan_teferi', 'Mizan Teferi'),
('sawla', 'Sawla'),
('mojo', 'Mojo'),
('dembi_dolo', 'Dembi Dolo'),
('aleta_wendo', 'Aleta Wendo'),
('metu', 'Metu'),
('mota', 'Mota'),
('fiche', 'Fiche'),
('finote_selam', 'Finote Selam'),
('bule_hora_town', 'Bule Hora Town'),
('bonga', 'Bonga'),
('kobo', 'Kobo'),
('jinka', 'Jinka'),
('dangila', 'Dangila'),
('degehabur', 'Degehabur'),
('bedessa', 'Bedessa'),
('agaro', 'Agaro') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;

134
db/query/bet_stat.sql Normal file
View File

@ -0,0 +1,134 @@
-- name: GetBetSummary :one
SELECT SUM(amount) as total_stakes,
COUNT(*) as total_bets,
SUM(
CASE
WHEN status = 0 THEN 1
ELSE 0
END
) as active_bets,
SUM(
CASE
WHEN status = 1 THEN 1
ELSE 0
END
) as total_wins,
SUM(
CASE
WHEN status = 2 THEN 1
ELSE 0
END
) as total_losses,
SUM(
CASE
WHEN status = 1 THEN amount * total_odds
ELSE 0
END
) as win_balance
FROM bets
wHERE (
user_id = sqlc.narg('user_id')
OR sqlc.narg('user_id') IS NULL
)
AND (
created_at > sqlc.narg('created_before')
OR sqlc.narg('created_before') IS NULL
)
AND (
created_at < sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL
);
-- name: GetBetStats :many
SELECT DATE(created_at) as date,
COUNT(*) as total_bets,
SUM(amount) as total_stakes,
SUM(
CASE
WHEN status = 1 THEN 1
ELSE 0
END
) as total_wins,
SUM(
CASE
WHEN status = 1 THEN amount * total_odds
ELSE 0
END
) as total_payouts,
AVG(total_odds) as average_odds
FROM bets
wHERE (
user_id = sqlc.narg('user_id')
OR sqlc.narg('user_id') IS NULL
)
AND (
is_shop_bet = sqlc.narg('is_shop_bet')
OR sqlc.narg('is_shop_bet') IS NULL
)
AND (
cashed_out = sqlc.narg('cashed_out')
OR sqlc.narg('cashed_out') IS NULL
)
AND (
full_name ILIKE '%' || sqlc.narg('query') || '%'
OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
created_at > sqlc.narg('created_before')
OR sqlc.narg('created_before') IS NULL
)
AND (
created_at < sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL
)
GROUP BY DATE(created_at)
ORDER BY DATE(created_at);
-- name: GetTotalBetsMadeInRange :one
SELECT COUNT(*) AS total_bets
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
-- name: GetTotalCashMadeInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
-- name: GetTotalCashOutInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
AND cashed_out = true;
-- name: GetTotalCashBacksInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
AND status = 5;
-- name: GetMarketPopularity :one
WITH market_counts AS (
SELECT DATE(b.created_at) as date,
bo.market_name,
COUNT(*) as bet_count,
ROW_NUMBER() OVER (
PARTITION BY DATE(b.created_at)
ORDER BY COUNT(*) DESC
) as rank
FROM bets b
JOIN bet_outcomes bo ON b.id = bo.bet_id
WHERE bo.market_name IS NOT NULL
AND (
user_id = sqlc.narg('user_id')
OR sqlc.narg('user_id') IS NULL
)
AND (
created_at > sqlc.narg('created_before')
OR sqlc.narg('created_before') IS NULL
)
AND (
created_at < sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL
)
GROUP BY DATE(b.created_at),
bo.market_name
)
SELECT date,
market_name
FROM market_counts
WHERE rank = 1;

View File

@ -2,9 +2,10 @@
INSERT INTO companies ( INSERT INTO companies (
name, name,
admin_id, admin_id,
wallet_id wallet_id,
deducted_percentage
) )
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4)
RETURNING *; RETURNING *;
-- name: GetAllCompanies :many -- name: GetAllCompanies :many
SELECT * SELECT *
@ -36,6 +37,11 @@ WHERE name ILIKE '%' || $1 || '%';
UPDATE companies UPDATE companies
SET name = COALESCE(sqlc.narg(name), name), SET name = COALESCE(sqlc.narg(name), name),
admin_id = COALESCE(sqlc.narg(admin_id), admin_id), admin_id = COALESCE(sqlc.narg(admin_id), admin_id),
is_active = COALESCE(sqlc.narg(is_active), is_active),
deducted_percentage = COALESCE(
sqlc.narg(deducted_percentage),
deducted_percentage
),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING *; RETURNING *;

View File

@ -168,6 +168,10 @@ WHERE is_live = false
AND ( AND (
leagues.country_code = sqlc.narg('country_code') leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL OR sqlc.narg('country_code') IS NULL
)
AND (
flagged = sqlc.narg('flagged')
OR sqlc.narg('flagged') IS NULL
); );
-- name: GetPaginatedUpcomingEvents :many -- name: GetPaginatedUpcomingEvents :many
SELECT events.*, SELECT events.*,
@ -197,6 +201,10 @@ WHERE start_time > now()
leagues.country_code = sqlc.narg('country_code') leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL OR sqlc.narg('country_code') IS NULL
) )
AND (
flagged = sqlc.narg('flagged')
OR sqlc.narg('flagged') IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetUpcomingByID :one -- name: GetUpcomingByID :one

View File

@ -1,21 +1,4 @@
-- name: GetTotalBetsMadeInRange :one
SELECT COUNT(*) AS total_bets
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
-- name: GetTotalCashMadeInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
-- name: GetTotalCashOutInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
AND cashed_out = true;
-- name: GetTotalCashBacksInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
FROM bets
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
AND status = 5;
-- name: GetCompanyWiseReport :many -- name: GetCompanyWiseReport :many
SELECT b.company_id, SELECT b.company_id,
c.name AS company_name, c.name AS company_name,

View File

@ -11,3 +11,25 @@ VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
UPDATE UPDATE
SET value = EXCLUDED.value SET value = EXCLUDED.value
RETURNING *; 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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

307
gen/db/bet_stat.sql.go Normal file
View File

@ -0,0 +1,307 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: bet_stat.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const GetBetStats = `-- name: GetBetStats :many
SELECT DATE(created_at) as date,
COUNT(*) as total_bets,
SUM(amount) as total_stakes,
SUM(
CASE
WHEN status = 1 THEN 1
ELSE 0
END
) as total_wins,
SUM(
CASE
WHEN status = 1 THEN amount * total_odds
ELSE 0
END
) as total_payouts,
AVG(total_odds) as average_odds
FROM bets
wHERE (
user_id = $1
OR $1 IS NULL
)
AND (
is_shop_bet = $2
OR $2 IS NULL
)
AND (
cashed_out = $3
OR $3 IS NULL
)
AND (
full_name ILIKE '%' || $4 || '%'
OR phone_number ILIKE '%' || $4 || '%'
OR $4 IS NULL
)
AND (
created_at > $5
OR $5 IS NULL
)
AND (
created_at < $6
OR $6 IS NULL
)
GROUP BY DATE(created_at)
ORDER BY DATE(created_at)
`
type GetBetStatsParams struct {
UserID pgtype.Int8 `json:"user_id"`
IsShopBet pgtype.Bool `json:"is_shop_bet"`
CashedOut pgtype.Bool `json:"cashed_out"`
Query pgtype.Text `json:"query"`
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
}
type GetBetStatsRow struct {
Date pgtype.Date `json:"date"`
TotalBets int64 `json:"total_bets"`
TotalStakes int64 `json:"total_stakes"`
TotalWins int64 `json:"total_wins"`
TotalPayouts int64 `json:"total_payouts"`
AverageOdds float64 `json:"average_odds"`
}
func (q *Queries) GetBetStats(ctx context.Context, arg GetBetStatsParams) ([]GetBetStatsRow, error) {
rows, err := q.db.Query(ctx, GetBetStats,
arg.UserID,
arg.IsShopBet,
arg.CashedOut,
arg.Query,
arg.CreatedBefore,
arg.CreatedAfter,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetBetStatsRow
for rows.Next() {
var i GetBetStatsRow
if err := rows.Scan(
&i.Date,
&i.TotalBets,
&i.TotalStakes,
&i.TotalWins,
&i.TotalPayouts,
&i.AverageOdds,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetBetSummary = `-- name: GetBetSummary :one
SELECT SUM(amount) as total_stakes,
COUNT(*) as total_bets,
SUM(
CASE
WHEN status = 0 THEN 1
ELSE 0
END
) as active_bets,
SUM(
CASE
WHEN status = 1 THEN 1
ELSE 0
END
) as total_wins,
SUM(
CASE
WHEN status = 2 THEN 1
ELSE 0
END
) as total_losses,
SUM(
CASE
WHEN status = 1 THEN amount * total_odds
ELSE 0
END
) as win_balance
FROM bets
wHERE (
user_id = $1
OR $1 IS NULL
)
AND (
created_at > $2
OR $2 IS NULL
)
AND (
created_at < $3
OR $3 IS NULL
)
`
type GetBetSummaryParams struct {
UserID pgtype.Int8 `json:"user_id"`
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
}
type GetBetSummaryRow struct {
TotalStakes int64 `json:"total_stakes"`
TotalBets int64 `json:"total_bets"`
ActiveBets int64 `json:"active_bets"`
TotalWins int64 `json:"total_wins"`
TotalLosses int64 `json:"total_losses"`
WinBalance int64 `json:"win_balance"`
}
func (q *Queries) GetBetSummary(ctx context.Context, arg GetBetSummaryParams) (GetBetSummaryRow, error) {
row := q.db.QueryRow(ctx, GetBetSummary, arg.UserID, arg.CreatedBefore, arg.CreatedAfter)
var i GetBetSummaryRow
err := row.Scan(
&i.TotalStakes,
&i.TotalBets,
&i.ActiveBets,
&i.TotalWins,
&i.TotalLosses,
&i.WinBalance,
)
return i, err
}
const GetMarketPopularity = `-- name: GetMarketPopularity :one
WITH market_counts AS (
SELECT DATE(b.created_at) as date,
bo.market_name,
COUNT(*) as bet_count,
ROW_NUMBER() OVER (
PARTITION BY DATE(b.created_at)
ORDER BY COUNT(*) DESC
) as rank
FROM bets b
JOIN bet_outcomes bo ON b.id = bo.bet_id
WHERE bo.market_name IS NOT NULL
AND (
user_id = $1
OR $1 IS NULL
)
AND (
created_at > $2
OR $2 IS NULL
)
AND (
created_at < $3
OR $3 IS NULL
)
GROUP BY DATE(b.created_at),
bo.market_name
)
SELECT date,
market_name
FROM market_counts
WHERE rank = 1
`
type GetMarketPopularityParams struct {
UserID pgtype.Int8 `json:"user_id"`
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
}
type GetMarketPopularityRow struct {
Date pgtype.Date `json:"date"`
MarketName string `json:"market_name"`
}
func (q *Queries) GetMarketPopularity(ctx context.Context, arg GetMarketPopularityParams) (GetMarketPopularityRow, error) {
row := q.db.QueryRow(ctx, GetMarketPopularity, arg.UserID, arg.CreatedBefore, arg.CreatedAfter)
var i GetMarketPopularityRow
err := row.Scan(&i.Date, &i.MarketName)
return i, err
}
const GetTotalBetsMadeInRange = `-- name: GetTotalBetsMadeInRange :one
SELECT COUNT(*) AS total_bets
FROM bets
WHERE created_at BETWEEN $1 AND $2
`
type GetTotalBetsMadeInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalBetsMadeInRange(ctx context.Context, arg GetTotalBetsMadeInRangeParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalBetsMadeInRange, arg.From, arg.To)
var total_bets int64
err := row.Scan(&total_bets)
return total_bets, err
}
const GetTotalCashBacksInRange = `-- name: GetTotalCashBacksInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
FROM bets
WHERE created_at BETWEEN $1 AND $2
AND status = 5
`
type GetTotalCashBacksInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashBacksInRange(ctx context.Context, arg GetTotalCashBacksInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashBacksInRange, arg.From, arg.To)
var total_cash_backs interface{}
err := row.Scan(&total_cash_backs)
return total_cash_backs, err
}
const GetTotalCashMadeInRange = `-- name: GetTotalCashMadeInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
FROM bets
WHERE created_at BETWEEN $1 AND $2
`
type GetTotalCashMadeInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashMadeInRange(ctx context.Context, arg GetTotalCashMadeInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashMadeInRange, arg.From, arg.To)
var total_cash_made interface{}
err := row.Scan(&total_cash_made)
return total_cash_made, err
}
const GetTotalCashOutInRange = `-- name: GetTotalCashOutInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
FROM bets
WHERE created_at BETWEEN $1 AND $2
AND cashed_out = true
`
type GetTotalCashOutInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashOutInRange(ctx context.Context, arg GetTotalCashOutInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashOutInRange, arg.From, arg.To)
var total_cash_out interface{}
err := row.Scan(&total_cash_out)
return total_cash_out, err
}

View File

@ -15,26 +15,35 @@ const CreateCompany = `-- name: CreateCompany :one
INSERT INTO companies ( INSERT INTO companies (
name, name,
admin_id, admin_id,
wallet_id wallet_id,
deducted_percentage
) )
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4)
RETURNING id, name, admin_id, wallet_id, created_at, updated_at RETURNING id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at
` `
type CreateCompanyParams struct { type CreateCompanyParams struct {
Name string `json:"name"` Name string `json:"name"`
AdminID int64 `json:"admin_id"` AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"` WalletID int64 `json:"wallet_id"`
DeductedPercentage float32 `json:"deducted_percentage"`
} }
func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (Company, error) { func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (Company, error) {
row := q.db.QueryRow(ctx, CreateCompany, arg.Name, arg.AdminID, arg.WalletID) row := q.db.QueryRow(ctx, CreateCompany,
arg.Name,
arg.AdminID,
arg.WalletID,
arg.DeductedPercentage,
)
var i Company var i Company
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -52,7 +61,7 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
} }
const GetAllCompanies = `-- name: GetAllCompanies :many const GetAllCompanies = `-- name: GetAllCompanies :many
SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number SELECT id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
FROM companies_details FROM companies_details
WHERE ( WHERE (
name ILIKE '%' || $1 || '%' name ILIKE '%' || $1 || '%'
@ -91,10 +100,12 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Balance, &i.Balance,
&i.IsActive, &i.WalletIsActive,
&i.AdminFirstName, &i.AdminFirstName,
&i.AdminLastName, &i.AdminLastName,
&i.AdminPhoneNumber, &i.AdminPhoneNumber,
@ -110,7 +121,7 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
} }
const GetCompanyByID = `-- name: GetCompanyByID :one const GetCompanyByID = `-- name: GetCompanyByID :one
SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number SELECT id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
FROM companies_details FROM companies_details
WHERE id = $1 WHERE id = $1
` `
@ -123,10 +134,12 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Balance, &i.Balance,
&i.IsActive, &i.WalletIsActive,
&i.AdminFirstName, &i.AdminFirstName,
&i.AdminLastName, &i.AdminLastName,
&i.AdminPhoneNumber, &i.AdminPhoneNumber,
@ -135,7 +148,7 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
} }
const SearchCompanyByName = `-- name: SearchCompanyByName :many const SearchCompanyByName = `-- name: SearchCompanyByName :many
SELECT id, name, admin_id, wallet_id, created_at, updated_at, balance, is_active, admin_first_name, admin_last_name, admin_phone_number SELECT id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
FROM companies_details FROM companies_details
WHERE name ILIKE '%' || $1 || '%' WHERE name ILIKE '%' || $1 || '%'
` `
@ -154,10 +167,12 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text)
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Balance, &i.Balance,
&i.IsActive, &i.WalletIsActive,
&i.AdminFirstName, &i.AdminFirstName,
&i.AdminLastName, &i.AdminLastName,
&i.AdminPhoneNumber, &i.AdminPhoneNumber,
@ -176,25 +191,40 @@ const UpdateCompany = `-- name: UpdateCompany :one
UPDATE companies UPDATE companies
SET name = COALESCE($2, name), SET name = COALESCE($2, name),
admin_id = COALESCE($3, admin_id), admin_id = COALESCE($3, admin_id),
is_active = COALESCE($4, is_active),
deducted_percentage = COALESCE(
$5,
deducted_percentage
),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING id, name, admin_id, wallet_id, created_at, updated_at RETURNING id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at
` `
type UpdateCompanyParams struct { type UpdateCompanyParams struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name pgtype.Text `json:"name"` Name pgtype.Text `json:"name"`
AdminID pgtype.Int8 `json:"admin_id"` AdminID pgtype.Int8 `json:"admin_id"`
IsActive pgtype.Bool `json:"is_active"`
DeductedPercentage pgtype.Float4 `json:"deducted_percentage"`
} }
func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (Company, error) { func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (Company, error) {
row := q.db.QueryRow(ctx, UpdateCompany, arg.ID, arg.Name, arg.AdminID) row := q.db.QueryRow(ctx, UpdateCompany,
arg.ID,
arg.Name,
arg.AdminID,
arg.IsActive,
arg.DeductedPercentage,
)
var i Company var i Company
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )

View File

@ -187,8 +187,12 @@ WHERE start_time > now()
leagues.country_code = $5 leagues.country_code = $5
OR $5 IS NULL OR $5 IS NULL
) )
AND (
flagged = $6
OR $6 IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT $7 OFFSET $6 LIMIT $8 OFFSET $7
` `
type GetPaginatedUpcomingEventsParams struct { type GetPaginatedUpcomingEventsParams struct {
@ -197,6 +201,7 @@ type GetPaginatedUpcomingEventsParams struct {
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"`
Offset pgtype.Int4 `json:"offset"` Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"` Limit pgtype.Int4 `json:"limit"`
} }
@ -235,6 +240,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode, arg.CountryCode,
arg.Flagged,
arg.Offset, arg.Offset,
arg.Limit, arg.Limit,
) )
@ -307,6 +313,10 @@ WHERE is_live = false
leagues.country_code = $5 leagues.country_code = $5
OR $5 IS NULL OR $5 IS NULL
) )
AND (
flagged = $6
OR $6 IS NULL
)
` `
type GetTotalEventsParams struct { type GetTotalEventsParams struct {
@ -315,6 +325,7 @@ type GetTotalEventsParams struct {
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"`
} }
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) { func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
@ -324,6 +335,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode, arg.CountryCode,
arg.Flagged,
) )
var count int64 var count int64
err := row.Scan(&count) err := row.Scan(&count)

View File

@ -173,26 +173,30 @@ type BranchOperation struct {
} }
type CompaniesDetail struct { type CompaniesDetail struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
AdminID int64 `json:"admin_id"` AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"` WalletID int64 `json:"wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"` DeductedPercentage float32 `json:"deducted_percentage"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` IsActive bool `json:"is_active"`
Balance int64 `json:"balance"` CreatedAt pgtype.Timestamp `json:"created_at"`
IsActive bool `json:"is_active"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
AdminFirstName string `json:"admin_first_name"` Balance int64 `json:"balance"`
AdminLastName string `json:"admin_last_name"` WalletIsActive bool `json:"wallet_is_active"`
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"` AdminFirstName string `json:"admin_first_name"`
AdminLastName string `json:"admin_last_name"`
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
} }
type Company struct { type Company struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
AdminID int64 `json:"admin_id"` AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"` WalletID int64 `json:"wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"` DeductedPercentage float32 `json:"deducted_percentage"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type CustomerWallet struct { type CustomerWallet struct {

View File

@ -156,77 +156,3 @@ func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseRe
} }
return items, nil return items, nil
} }
const GetTotalBetsMadeInRange = `-- name: GetTotalBetsMadeInRange :one
SELECT COUNT(*) AS total_bets
FROM bets
WHERE created_at BETWEEN $1 AND $2
`
type GetTotalBetsMadeInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalBetsMadeInRange(ctx context.Context, arg GetTotalBetsMadeInRangeParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalBetsMadeInRange, arg.From, arg.To)
var total_bets int64
err := row.Scan(&total_bets)
return total_bets, err
}
const GetTotalCashBacksInRange = `-- name: GetTotalCashBacksInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
FROM bets
WHERE created_at BETWEEN $1 AND $2
AND status = 5
`
type GetTotalCashBacksInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashBacksInRange(ctx context.Context, arg GetTotalCashBacksInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashBacksInRange, arg.From, arg.To)
var total_cash_backs interface{}
err := row.Scan(&total_cash_backs)
return total_cash_backs, err
}
const GetTotalCashMadeInRange = `-- name: GetTotalCashMadeInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
FROM bets
WHERE created_at BETWEEN $1 AND $2
`
type GetTotalCashMadeInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashMadeInRange(ctx context.Context, arg GetTotalCashMadeInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashMadeInRange, arg.From, arg.To)
var total_cash_made interface{}
err := row.Scan(&total_cash_made)
return total_cash_made, err
}
const GetTotalCashOutInRange = `-- name: GetTotalCashOutInRange :one
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
FROM bets
WHERE created_at BETWEEN $1 AND $2
AND cashed_out = true
`
type GetTotalCashOutInRangeParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
func (q *Queries) GetTotalCashOutInRange(ctx context.Context, arg GetTotalCashOutInRangeParams) (interface{}, error) {
row := q.db.QueryRow(ctx, GetTotalCashOutInRange, arg.From, arg.To)
var total_cash_out interface{}
err := row.Scan(&total_cash_out)
return total_cash_out, err
}

View File

@ -95,6 +95,7 @@ type CreateBetOutcomeReq struct {
type CreateBetReq struct { type CreateBetReq struct {
Outcomes []CreateBetOutcomeReq `json:"outcomes"` Outcomes []CreateBetOutcomeReq `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"` Amount float32 `json:"amount" example:"100.0"`
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
} }
type RandomBetReq struct { type RandomBetReq struct {

View File

@ -74,3 +74,98 @@ type CreateBranchOperation struct {
BranchID int64 BranchID int64
OperationID int64 OperationID int64
} }
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"`
}
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"`
}
type CreateSupportedOperationReq struct {
Name string `json:"name" example:"SportsBook"`
Description string `json:"description" example:"Betting on sport events"`
}
type SupportedOperationRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"SportsBook"`
Description string `json:"description" example:"Betting on sport events"`
}
type CreateBranchOperationReq struct {
BranchID int64 `json:"branch_id" example:"1"`
OperationID int64 `json:"operation_id" example:"1"`
}
type BranchOperationRes struct {
Name string `json:"name" example:"SportsBook"`
Description string `json:"description" example:"Betting on sport events"`
}
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"`
}
type BranchDetailRes 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"`
ManagerName string `json:"manager_name" example:"John Smith"`
ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"`
Balance float32 `json:"balance" example:"100.5"`
IsActive bool `json:"is_active" example:"false"`
WalletIsActive bool `json:"is_wallet_active" example:"false"`
}
func ConvertBranch(branch 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,
}
}
func ConvertBranchDetail(branch BranchDetail) BranchDetailRes {
return BranchDetailRes{
ID: branch.ID,
Name: branch.Name,
Location: branch.Location,
WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
ManagerName: branch.ManagerName,
ManagerPhoneNumber: branch.ManagerPhoneNumber,
Balance: branch.Balance.Float32(),
IsActive: branch.IsActive,
WalletIsActive: branch.WalletIsActive,
}
}

View File

@ -22,6 +22,11 @@ type ValidInt32 struct {
Valid bool Valid bool
} }
type ValidFloat32 struct {
Value float32
Valid bool
}
type ValidString struct { type ValidString struct {
Value string Value string
Valid bool Valid bool

View File

@ -1,42 +1,226 @@
package domain package domain
import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/jackc/pgx/v5/pgtype"
)
// Company represents the client that we will contract the services with // Company represents the client that we will contract the services with
// they are the ones that manage the branches and branch managers // they are the ones that manage the branches and branch managers
// they will have their own wallet that they will use to distribute to the branch wallets // they will have their own wallet that they will use to distribute to the branch wallets
type Company struct { type Company struct {
ID int64 ID int64
Name string Name string
AdminID int64 AdminID int64
WalletID int64 WalletID int64
DeductedPercentage float32
IsActive bool
} }
type CompanyFilter struct { type CompanyFilter struct {
IsActive ValidBool IsActive ValidBool
Query ValidString Query ValidString
CreatedBefore ValidTime CreatedBefore ValidTime
CreatedAfter ValidTime CreatedAfter ValidTime
} }
type GetCompany struct { type GetCompany struct {
ID int64 ID int64
Name string Name string
AdminID int64 AdminID int64
AdminFirstName string AdminFirstName string
AdminLastName string AdminLastName string
AdminPhoneNumber string AdminPhoneNumber string
WalletID int64 WalletID int64
WalletBalance Currency WalletBalance Currency
IsWalletActive bool IsWalletActive bool
DeductedPercentage float32
IsActive bool
} }
type CreateCompany struct { type CreateCompany struct {
Name string Name string
AdminID int64 AdminID int64
WalletID int64 WalletID int64
DeductedPercentage float32
} }
type UpdateCompany struct { type UpdateCompany struct {
ID int64 ID int64
Name *string Name ValidString
AdminID *int64 AdminID ValidInt64
IsActive ValidBool
DeductedPercentage ValidFloat32
}
type CreateCompanyReq struct {
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
}
type UpdateCompanyReq struct {
Name *string `json:"name,omitempty" example:"CompanyName"`
AdminID *int64 `json:"admin_id,omitempty" example:"1"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
DeductedPercentage *float32 `json:"deducted_percentage,omitempty" example:"0.1" validate:"lt=1"`
}
type CompanyRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
WalletID int64 `json:"wallet_id" example:"1"`
DeductedPercentage float32 `json:"deducted_percentage" example:"0.1"`
IsActive bool `json:"is_active" example:"true"`
}
type GetCompanyRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
WalletID int64 `json:"wallet_id" example:"1"`
WalletBalance float32 `json:"balance" example:"1"`
WalletIsActive bool `json:"is_wallet_active" example:"false"`
IsActive bool `json:"is_active" example:"false"`
DeductedPercentage float32 `json:"deducted_percentage" example:"0.1"`
AdminFirstName string `json:"admin_first_name" example:"John"`
AdminLastName string `json:"admin_last_name" example:"Doe"`
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
}
func ConvertCompany(company Company) CompanyRes {
return CompanyRes{
ID: company.ID,
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
IsActive: company.IsActive,
DeductedPercentage: company.DeductedPercentage,
}
}
func ConvertGetCompany(company GetCompany) GetCompanyRes {
return GetCompanyRes{
ID: company.ID,
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
WalletBalance: company.WalletBalance.Float32(),
IsActive: company.IsActive,
WalletIsActive: company.IsWalletActive,
DeductedPercentage: company.DeductedPercentage,
AdminFirstName: company.AdminFirstName,
AdminLastName: company.AdminLastName,
AdminPhoneNumber: company.AdminPhoneNumber,
}
}
func ConvertCreateCompany(company CreateCompany) dbgen.CreateCompanyParams {
return dbgen.CreateCompanyParams{
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
DeductedPercentage: company.DeductedPercentage,
}
}
func ConvertDBCompany(dbCompany dbgen.Company) Company {
return Company{
ID: dbCompany.ID,
Name: dbCompany.Name,
AdminID: dbCompany.AdminID,
WalletID: dbCompany.WalletID,
DeductedPercentage: dbCompany.DeductedPercentage,
IsActive: dbCompany.IsActive,
}
}
func ConvertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) GetCompany {
return GetCompany{
ID: dbCompany.ID,
Name: dbCompany.Name,
AdminID: dbCompany.AdminID,
WalletID: dbCompany.WalletID,
WalletBalance: Currency(dbCompany.Balance),
IsWalletActive: dbCompany.WalletIsActive,
AdminFirstName: dbCompany.AdminFirstName,
AdminLastName: dbCompany.AdminLastName,
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
DeductedPercentage: dbCompany.DeductedPercentage,
IsActive: dbCompany.IsActive,
}
}
func ConvertUpdateCompany(updateCompany UpdateCompany) dbgen.UpdateCompanyParams {
newUpdateCompany := dbgen.UpdateCompanyParams{
ID: updateCompany.ID,
Name: pgtype.Text{
String: updateCompany.Name.Value,
Valid: updateCompany.Name.Valid,
},
AdminID: pgtype.Int8{
Int64: updateCompany.AdminID.Value,
Valid: updateCompany.AdminID.Valid,
},
IsActive: pgtype.Bool{
Bool: updateCompany.IsActive.Value,
Valid: updateCompany.IsActive.Valid,
},
DeductedPercentage: pgtype.Float4{
Float32: updateCompany.DeductedPercentage.Value,
Valid: updateCompany.DeductedPercentage.Valid,
},
}
return newUpdateCompany
}
func ConvertUpdateCompanyReq(req UpdateCompanyReq) UpdateCompany {
var updateCompany UpdateCompany
if req.Name != nil {
updateCompany.Name = ValidString{
Value: *req.Name,
Valid: true,
}
}
if req.AdminID != nil {
updateCompany.AdminID = ValidInt64{
Value: *req.AdminID,
Valid: true,
}
}
if req.IsActive != nil {
updateCompany.IsActive = ValidBool{
Value: *req.IsActive,
Valid: true,
}
}
if req.DeductedPercentage != nil {
updateCompany.DeductedPercentage = ValidFloat32{
Value: *req.DeductedPercentage,
Valid: true,
}
}
return updateCompany
}
func ConvertGetAllCompaniesParams(filter CompanyFilter) dbgen.GetAllCompaniesParams {
return dbgen.GetAllCompaniesParams{
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
CreatedBefore: pgtype.Timestamp{
Time: filter.CreatedBefore.Value,
Valid: filter.CreatedBefore.Valid,
},
CreatedAfter: pgtype.Timestamp{
Time: filter.CreatedAfter.Value,
Valid: filter.CreatedAfter.Valid,
},
}
} }

View File

@ -128,4 +128,5 @@ type EventFilter struct {
Limit ValidInt64 Limit ValidInt64
Offset ValidInt64 Offset ValidInt64
MatchStatus ValidString // e.g., "upcoming", "in_play", "ended" MatchStatus ValidString // e.g., "upcoming", "in_play", "ended"
Flagged ValidBool
} }

View File

@ -1,6 +1,8 @@
package domain package domain
import "time" import (
"time"
)
type Setting struct { type Setting struct {
Key string Key string
@ -15,8 +17,37 @@ type SettingRes struct {
} }
type SettingList struct { type SettingList struct {
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
BetAmountLimit Currency `json:"bet_amount_limit"` BetAmountLimit Currency `json:"bet_amount_limit"`
DailyTicketPerIP int64 `json:"daily_ticket_limit"` DailyTicketPerIP int64 `json:"daily_ticket_limit"`
TotalWinningLimit Currency `json:"total_winning_limit"` TotalWinningLimit Currency `json:"total_winning_limit"`
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
}
type DBSettingList struct {
MaxNumberOfOutcomes ValidInt64
BetAmountLimit ValidInt64
DailyTicketPerIP ValidInt64
TotalWinningLimit ValidInt64
AmountForBetReferral ValidInt64
}
func ConvertInt64SettingsMap(dbSettingList *DBSettingList) map[string]*ValidInt64 {
return map[string]*ValidInt64{
"max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes,
"bet_amount_limit": &dbSettingList.BetAmountLimit,
"daily_ticket_limit": &dbSettingList.DailyTicketPerIP,
"total_winnings_limit": &dbSettingList.TotalWinningLimit,
"amount_for_bet_referral": &dbSettingList.AmountForBetReferral,
}
}
func ConvertDBSetting(dbSettingList DBSettingList) SettingList {
return SettingList{
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
BetAmountLimit: Currency(dbSettingList.BetAmountLimit.Value),
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
TotalWinningLimit: Currency(dbSettingList.TotalWinningLimit.Value),
AmountForBetReferral: Currency(dbSettingList.AmountForBetReferral.Value),
}
} }

View File

@ -187,3 +187,7 @@ func ConvertShopTransactionDetail(transaction ShopTransactionDetail) ShopTransac
return newTransaction return newTransaction
} }
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" example:"true"`
}

View File

@ -406,7 +406,7 @@ func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
// argPos++ // argPos++
// } // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND %suser_id = $%d", func() string { query += fmt.Sprintf(" %suser_id = $%d", func() string {
if len(args) == 0 { if len(args) == 0 {
return " WHERE " return " WHERE "
} }
@ -480,21 +480,21 @@ func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" WHERE company_id = $%d", argPos) // query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string { // query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
if len(args) == 0 { // if len(args) == 0 {
return " WHERE " // return " WHERE "
} // }
return " AND " // return " AND "
}(), argPos) // }(), argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND %suser_id = $%d", func() string { query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
if len(args) == 0 { if len(args) == 0 {
@ -594,16 +594,16 @@ func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilt
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) // query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos) // query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos) query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)
@ -684,16 +684,6 @@ func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFil
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos) query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)
@ -770,21 +760,21 @@ func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" WHERE company_id = $%d", argPos) // query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string { // query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
if len(args) == 0 { // if len(args) == 0 {
return " WHERE " // return " WHERE "
} // }
return " AND " // return " AND "
}(), argPos) // }(), argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND %suser_id = $%d", func() string { query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
if len(args) == 0 { if len(args) == 0 {
@ -880,16 +870,16 @@ func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.Report
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND company_id = $%d", argPos) // query += fmt.Sprintf(" AND company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND branch_id = $%d", argPos) // query += fmt.Sprintf(" AND branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND user_id = $%d", argPos) query += fmt.Sprintf(" AND user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)
@ -975,16 +965,16 @@ func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFi
argPos := 1 argPos := 1
// Add filters if provided // Add filters if provided
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND company_id = $%d", argPos) // query += fmt.Sprintf(" AND company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND branch_id = $%d", argPos) // query += fmt.Sprintf(" AND branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.StartTime.Valid { if filter.StartTime.Valid {
query += fmt.Sprintf(" AND created_at >= $%d", argPos) query += fmt.Sprintf(" AND created_at >= $%d", argPos)
args = append(args, filter.StartTime.Value) args = append(args, filter.StartTime.Value)
@ -1059,16 +1049,16 @@ func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFil
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) // query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos) // query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos) query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)
@ -1144,16 +1134,16 @@ func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter)
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) // query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos) // query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos) query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)
@ -1220,16 +1210,16 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
args := []interface{}{} args := []interface{}{}
argPos := 1 argPos := 1
if filter.CompanyID.Valid { // if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND b.company_id = $%d", argPos) // query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value) // args = append(args, filter.CompanyID.Value)
argPos++ // argPos++
} // }
if filter.BranchID.Valid { // if filter.BranchID.Valid {
query += fmt.Sprintf(" AND b.branch_id = $%d", argPos) // query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value) // args = append(args, filter.BranchID.Value)
argPos++ // argPos++
} // }
if filter.UserID.Valid { if filter.UserID.Valid {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos) query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value) args = append(args, filter.UserID.Value)

View File

@ -3,95 +3,28 @@ package repository
import ( import (
"context" "context"
"fmt" "fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams {
return dbgen.CreateCompanyParams{
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
}
}
func convertDBCompany(dbCompany dbgen.Company) domain.Company {
return domain.Company{
ID: dbCompany.ID,
Name: dbCompany.Name,
AdminID: dbCompany.AdminID,
WalletID: dbCompany.WalletID,
}
}
func convertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) domain.GetCompany {
return domain.GetCompany{
ID: dbCompany.ID,
Name: dbCompany.Name,
AdminID: dbCompany.AdminID,
WalletID: dbCompany.WalletID,
WalletBalance: domain.Currency(dbCompany.Balance),
IsWalletActive: dbCompany.IsActive,
AdminFirstName: dbCompany.AdminFirstName,
AdminLastName: dbCompany.AdminLastName,
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
}
}
func convertUpdateCompany(updateCompany domain.UpdateCompany) dbgen.UpdateCompanyParams {
var newUpdateCompany dbgen.UpdateCompanyParams
newUpdateCompany.ID = updateCompany.ID
if updateCompany.Name != nil {
newUpdateCompany.Name = pgtype.Text{
String: *updateCompany.Name,
Valid: true,
}
}
if updateCompany.AdminID != nil {
newUpdateCompany.AdminID = pgtype.Int8{
Int64: *updateCompany.AdminID,
Valid: true,
}
}
return newUpdateCompany
}
func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company)) dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company))
if err != nil { if err != nil {
return domain.Company{}, err return domain.Company{}, err
} }
return convertDBCompany(dbCompany), nil return domain.ConvertDBCompany(dbCompany), nil
} }
func (s *Store) GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) { func (s *Store) GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) {
dbCompanies, err := s.queries.GetAllCompanies(ctx, dbgen.GetAllCompaniesParams{ dbCompanies, err := s.queries.GetAllCompanies(ctx, domain.ConvertGetAllCompaniesParams(filter))
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
CreatedBefore: pgtype.Timestamp{
Time: filter.CreatedBefore.Value,
Valid: filter.CreatedBefore.Valid,
},
CreatedAfter: pgtype.Timestamp{
Time: filter.CreatedAfter.Value,
Valid: filter.CreatedAfter.Valid,
},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies)) var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies { for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompanyDetails(dbCompany)) companies = append(companies, domain.ConvertDBCompanyDetails(dbCompany))
} }
return companies, nil return companies, nil
@ -109,7 +42,7 @@ func (s *Store) SearchCompanyByName(ctx context.Context, name string) ([]domain.
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies)) var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies { for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompanyDetails(dbCompany)) companies = append(companies, domain.ConvertDBCompanyDetails(dbCompany))
} }
return companies, nil return companies, nil
} }
@ -120,17 +53,17 @@ func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany
if err != nil { if err != nil {
return domain.GetCompany{}, err return domain.GetCompany{}, err
} }
return convertDBCompanyDetails(dbCompany), nil return domain.ConvertDBCompanyDetails(dbCompany), nil
} }
func (s *Store) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) { func (s *Store) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) {
dbCompany, err := s.queries.UpdateCompany(ctx, convertUpdateCompany(company)) dbCompany, err := s.queries.UpdateCompany(ctx, domain.ConvertUpdateCompany(company))
if err != nil { if err != nil {
return domain.Company{}, err return domain.Company{}, err
} }
return convertDBCompany(dbCompany), nil return domain.ConvertDBCompany(dbCompany), nil
} }
func (s *Store) DeleteCompany(ctx context.Context, id int64) error { func (s *Store) DeleteCompany(ctx context.Context, id int64) error {

View File

@ -89,6 +89,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
@ -121,6 +122,7 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
@ -157,6 +159,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value, String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid, Valid: filter.CountryCode.Valid,
}, },
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
},
}) })
if err != nil { if err != nil {
@ -180,7 +186,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
} }
} }
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
@ -204,6 +210,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value, String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid, Valid: filter.CountryCode.Valid,
}, },
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
},
}) })
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -234,6 +244,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
StartTime: event.StartTime.Time.UTC(), StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String, Source: event.Source.String,
Status: domain.EventStatus(event.Status.String), Status: domain.EventStatus(event.Status.String),
Flagged: event.Flagged,
}, nil }, nil
} }
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error { func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
@ -269,10 +280,9 @@ func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status do
} }
func (s *Store) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error { func (s *Store) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{ return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{
ID: eventID, ID: eventID,
Flagged: flagged, Flagged: flagged,
}) })
} }

View File

@ -10,21 +10,16 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type DBSettingList struct {
MaxNumberOfOutcomes domain.ValidInt64
BetAmountLimit domain.ValidInt64
DailyTicketPerIP domain.ValidInt64
TotalWinningLimit domain.ValidInt64
}
func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) { func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
var dbSettingList DBSettingList var dbSettingList domain.DBSettingList
var int64SettingsMap = map[string]*domain.ValidInt64{ // var int64SettingsMap = map[string]*domain.ValidInt64{
"max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes, // "max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes,
"bet_amount_limit": &dbSettingList.BetAmountLimit, // "bet_amount_limit": &dbSettingList.BetAmountLimit,
"daily_ticket_limit": &dbSettingList.DailyTicketPerIP, // "daily_ticket_limit": &dbSettingList.DailyTicketPerIP,
"total_winnings_limit": &dbSettingList.TotalWinningLimit, // "total_winnings_limit": &dbSettingList.TotalWinningLimit,
} // }
var int64SettingsMap = domain.ConvertInt64SettingsMap(&dbSettingList)
for _, setting := range settings { for _, setting := range settings {
is_setting_unknown := true is_setting_unknown := true
@ -54,12 +49,7 @@ func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
} }
} }
return domain.SettingList{ return domain.ConvertDBSetting(dbSettingList), nil
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
BetAmountLimit: domain.Currency(dbSettingList.BetAmountLimit.Value),
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
TotalWinningLimit: domain.Currency(dbSettingList.TotalWinningLimit.Value),
}, nil
} }
func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) { func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) {
settings, err := s.queries.GetSettings(ctx) settings, err := s.queries.GetSettings(ctx)

View File

@ -10,10 +10,8 @@ import (
type BetStore interface { type BetStore interface {
CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error)
CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error)
GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error)
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error)

View File

@ -19,6 +19,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
@ -39,8 +40,9 @@ var (
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet") ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet") ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
ErrInvalidAmount = errors.New("Invalid amount") ErrInvalidAmount = errors.New("Invalid amount")
ErrBetAmountTooHigh = errors.New("Cannot create a bet with an amount above limit") ErrBetAmountTooHigh = errors.New("Cannot create a bet with an amount above limit")
ErrBetWinningTooHigh = errors.New("Total Winnings over set limit")
) )
type Service struct { type Service struct {
@ -49,6 +51,7 @@ type Service struct {
prematchSvc odds.ServiceImpl prematchSvc odds.ServiceImpl
walletSvc wallet.Service walletSvc wallet.Service
branchSvc branch.Service branchSvc branch.Service
companySvc company.Service
settingSvc settings.Service settingSvc settings.Service
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
logger *slog.Logger logger *slog.Logger
@ -61,6 +64,7 @@ func NewService(
prematchSvc odds.ServiceImpl, prematchSvc odds.ServiceImpl,
walletSvc wallet.Service, walletSvc wallet.Service,
branchSvc branch.Service, branchSvc branch.Service,
companySvc company.Service,
settingSvc settings.Service, settingSvc settings.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
logger *slog.Logger, logger *slog.Logger,
@ -72,6 +76,7 @@ func NewService(
prematchSvc: prematchSvc, prematchSvc: prematchSvc,
walletSvc: walletSvc, walletSvc: walletSvc,
branchSvc: branchSvc, branchSvc: branchSvc,
companySvc: companySvc,
settingSvc: settingSvc, settingSvc: settingSvc,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
logger: logger, logger: logger,
@ -206,7 +211,7 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
return newOutcome, nil return newOutcome, nil
} }
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) { 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) settingsList, err := s.settingSvc.GetSettingList(ctx)
if req.Amount < 1 { if req.Amount < 1 {
@ -214,11 +219,11 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
} }
if req.Amount > settingsList.BetAmountLimit.Float32() { if req.Amount > settingsList.BetAmountLimit.Float32() {
return domain.CreateBetRes{}, ErrInvalidAmount return domain.CreateBetRes{}, ErrBetAmountTooHigh
} }
if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) { if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) {
s.mongoLogger.Error("too many outcomes", s.mongoLogger.Info("too many outcomes",
zap.Int("count", len(req.Outcomes)), zap.Int("count", len(req.Outcomes)),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
) )
@ -244,6 +249,16 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
outcomes = append(outcomes, newOutcome) outcomes = append(outcomes, newOutcome)
} }
totalWinnings := req.Amount * totalOdds
if totalWinnings > settingsList.TotalWinningLimit.Float32() {
s.mongoLogger.Info("Total Winnings over limit",
zap.Float32("Total Odds", totalOdds),
zap.Float32("amount", req.Amount),
zap.Float32("limit", settingsList.TotalWinningLimit.Float32()))
return domain.CreateBetRes{}, ErrBetWinningTooHigh
}
outcomesHash, err := generateOutcomeHash(outcomes) outcomesHash, err := generateOutcomeHash(outcomes)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate outcome hash", s.mongoLogger.Error("failed to generate outcome hash",
@ -254,14 +269,6 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
} }
count, err := s.GetBetCount(ctx, userID, outcomesHash) count, err := s.GetBetCount(ctx, userID, outcomesHash)
if err != nil {
return domain.CreateBetRes{}, err
}
if count >= 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already placed twice")
}
cashoutID, err := s.GenerateCashoutID()
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate cashout ID", s.mongoLogger.Error("failed to generate cashout ID",
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
@ -269,6 +276,9 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
) )
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
if count >= 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already placed twice")
}
fastCode := helpers.GenerateFastCode() fastCode := helpers.GenerateFastCode()
@ -276,15 +286,14 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds, TotalOdds: totalOdds,
Status: domain.OUTCOME_STATUS_PENDING, Status: domain.OUTCOME_STATUS_PENDING,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
CashoutID: cashoutID,
OutcomesHash: outcomesHash, OutcomesHash: outcomesHash,
FastCode: fastCode, FastCode: fastCode,
UserID: userID,
} }
switch role { switch role {
case domain.RoleCashier: case domain.RoleCashier:
newBet.IsShopBet = true
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get branch by cashier", s.mongoLogger.Error("failed to get branch by cashier",
@ -294,30 +303,20 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
deductedAmount := req.Amount / 10 err = s.DeductBetFromBranchWallet(ctx, req.Amount, branch.WalletID, branch.CompanyID, userID)
_, err = s.walletSvc.DeductFromWallet(ctx,
branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{
Value: userID,
Valid: true,
}, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
if err != nil { if err != nil {
s.mongoLogger.Error("failed to deduct from wallet", s.mongoLogger.Error("wallet deduction for bet failed",
zap.Int64("wallet_id", branch.WalletID), zap.String("role", string(role)),
zap.Float32("amount", deductedAmount),
zap.Error(err), zap.Error(err),
) )
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true}
newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true}
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
newBet.IsShopBet = true
case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin: case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin:
newBet.IsShopBet = true
// Branch Manager, Admin and Super Admin are required to pass a branch id if they want to create a bet
if req.BranchID == nil { if req.BranchID == nil {
s.mongoLogger.Error("branch ID required for admin/manager", s.mongoLogger.Warn("branch ID required for admin/manager",
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
) )
return domain.CreateBetRes{}, ErrBranchIDRequired return domain.CreateBetRes{}, ErrBranchIDRequired
@ -332,87 +331,42 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
deductedAmount := req.Amount / 10 if branch.BranchManagerID != userID {
_, err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{ s.mongoLogger.Warn("unauthorized branch for branch manager",
Value: userID, zap.Int64("branch_id", *req.BranchID),
Valid: true,
}, domain.TRANSFER_DIRECT, fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
if err != nil {
s.mongoLogger.Error("wallet deduction failed",
zap.Int64("wallet_id", branch.WalletID),
zap.Float32("amount", deductedAmount),
zap.Error(err), zap.Error(err),
) )
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true} if companyID.Valid && branch.CompanyID == companyID.Value {
newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true} s.mongoLogger.Warn("unauthorized company",
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true} zap.Int64("branch_id", *req.BranchID),
newBet.IsShopBet = true zap.Error(err),
)
}
err = s.DeductBetFromBranchWallet(ctx, req.Amount, branch.WalletID, branch.CompanyID, userID)
if err != nil {
s.mongoLogger.Error("wallet deduction for bet failed",
zap.String("role", string(role)),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
case domain.RoleCustomer: case domain.RoleCustomer:
wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID) // Only the customer is able to create a online bet
newBet.IsShopBet = false
err = s.DeductBetFromCustomerWallet(ctx, req.Amount, userID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get customer wallets", s.mongoLogger.Error("customer wallet deduction failed",
zap.Float32("amount", req.Amount),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.Error(err), zap.Error(err),
) )
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
if req.Amount < wallets.RegularBalance.Float32() {
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{},
domain.TRANSFER_DIRECT, fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", req.Amount))
if err != nil {
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
} else {
combinedBalance := wallets.RegularBalance + wallets.StaticBalance
if req.Amount > combinedBalance.Float32() {
return domain.CreateBetRes{}, ErrTotalBalanceNotEnough
}
// Empty the regular balance
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
wallets.RegularBalance, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
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",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
// Empty remaining from static balance
remainingAmount := wallets.RegularBalance - domain.Currency(req.Amount)
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.StaticID,
remainingAmount, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT,
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",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("static wallet_id", wallets.StaticID),
zap.Float32("amount", req.Amount),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
}
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
newBet.IsShopBet = false
default: default:
s.mongoLogger.Error("unknown role type", s.mongoLogger.Error("unknown role type",
zap.String("role", string(role)), zap.String("role", string(role)),
@ -447,6 +401,100 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return res, nil return res, nil
} }
func (s *Service) DeductBetFromBranchWallet(ctx context.Context, amount float32, walletID int64, companyID int64, userID int64) error {
company, err := s.companySvc.GetCompanyByID(ctx, companyID)
if err != nil {
s.mongoLogger.Error("failed to get company",
zap.Int64("company_id", companyID),
zap.Error(err),
)
return err
}
deductedAmount := amount * company.DeductedPercentage
_, err = s.walletSvc.DeductFromWallet(ctx,
walletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{
Value: userID,
Valid: true,
}, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
if err != nil {
s.mongoLogger.Error("failed to deduct from wallet",
zap.Int64("wallet_id", walletID),
zap.Float32("amount", deductedAmount),
zap.Error(err),
)
return err
}
return nil
}
func (s *Service) DeductBetFromCustomerWallet(ctx context.Context, amount float32, userID int64) error {
wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID)
if err != nil {
s.mongoLogger.Error("failed to get customer wallets",
zap.Int64("user_id", userID),
zap.Error(err),
)
return err
}
if amount < wallets.RegularBalance.Float32() {
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
domain.ToCurrency(amount), domain.CustomerWalletType, 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",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", amount),
zap.Error(err),
)
return err
}
} else {
combinedBalance := wallets.RegularBalance + wallets.StaticBalance
if amount > combinedBalance.Float32() {
return ErrTotalBalanceNotEnough
}
// Empty the regular balance
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
wallets.RegularBalance, domain.CustomerWalletType, 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",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("regular wallet_id", wallets.RegularID),
zap.Float32("amount", amount),
zap.Error(err),
)
return err
}
// 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,
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",
zap.Int64("customer_id", wallets.CustomerID),
zap.Int64("customer_wallet_id", wallets.ID),
zap.Int64("static wallet_id", wallets.StaticID),
zap.Float32("amount", amount),
zap.Error(err),
)
return err
}
}
return nil
}
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, sportID int32, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) { func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, sportID int32, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
var newOdds []domain.CreateBetOutcome var newOdds []domain.CreateBetOutcome
@ -657,26 +705,38 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
// s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds)) // s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
var cashoutID string outcomesHash, err := generateOutcomeHash(randomOdds)
cashoutID, err = s.GenerateCashoutID()
if err != nil { if err != nil {
s.mongoLogger.Error("Failed to generate cash out ID", s.mongoLogger.Error("failed to generate outcome hash",
zap.Int64("userID", userID), zap.Int64("user_id", userID),
zap.Int64("branchID", branchID)) zap.Error(err),
)
return domain.CreateBetRes{}, err return domain.CreateBetRes{}, err
} }
randomNumber := strconv.FormatInt(int64(random.Intn(100000000000)), 10) count, err := s.GetBetCount(ctx, userID, outcomesHash)
if err != nil {
s.mongoLogger.Error("failed to get bet count",
zap.Int64("user_id", userID),
zap.String("outcome_hash", outcomesHash),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
if count >= 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already placed twice")
}
fastCode := helpers.GenerateFastCode()
newBet := domain.CreateBet{ newBet := domain.CreateBet{
Amount: domain.ToCurrency(123.5), Amount: domain.ToCurrency(123.5),
TotalOdds: totalOdds, TotalOdds: totalOdds,
Status: domain.OUTCOME_STATUS_PENDING, Status: domain.OUTCOME_STATUS_PENDING,
FullName: "test" + randomNumber, UserID: userID,
PhoneNumber: "0900000000", IsShopBet: true,
CashoutID: cashoutID, FastCode: fastCode,
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
UserID: domain.ValidInt64{Valid: true, Value: userID},
} }
bet, err := s.CreateBet(ctx, newBet) bet, err := s.CreateBet(ctx, newBet)
@ -721,17 +781,10 @@ func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.Create
func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
return s.betStore.GetBetByID(ctx, id) return s.betStore.GetBetByID(ctx, id)
} }
func (s *Service) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
return s.betStore.GetBetByCashoutID(ctx, id)
}
func (s *Service) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) { func (s *Service) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) {
return s.betStore.GetAllBets(ctx, filter) return s.betStore.GetAllBets(ctx, filter)
} }
func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domain.GetBet, error) {
return s.betStore.GetBetByBranchID(ctx, branchID)
}
func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) { func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) {
return s.betStore.GetBetByUserID(ctx, UserID) return s.betStore.GetBetByUserID(ctx, UserID)
} }
@ -948,3 +1001,27 @@ func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {
sum := sha256.Sum256([]byte(sb.String())) sum := sha256.Sum256([]byte(sb.String()))
return hex.EncodeToString(sum[:]), nil return hex.EncodeToString(sum[:]), nil
} }
func (s *Service) CheckIfBetError(err error) bool {
betErrors := []error{
ErrNoEventsAvailable,
ErrGenerateRandomOutcome,
ErrOutcomesNotCompleted,
ErrEventHasBeenRemoved,
ErrEventHasNotEnded,
ErrRawOddInvalid,
ErrBranchIDRequired,
ErrOutcomeLimit,
ErrTotalBalanceNotEnough,
ErrInvalidAmount,
ErrBetAmountTooHigh,
ErrBetWinningTooHigh,
}
for _, e := range betErrors {
if errors.Is(err, e) {
return true
}
}
return false
}

View File

@ -649,7 +649,7 @@ func (s *Service) fetchReportData(ctx context.Context, period string) (domain.Re
totalCashBacks = 0 totalCashBacks = 0
} }
companyReports = append(companyReports, domain.CompanyReport{ companyReports = append(companyReports, domain.CompanyReport{
CompanyID: row.CompanyID.Int64, CompanyID: row.CompanyID,
CompanyName: row.CompanyName, CompanyName: row.CompanyName,
TotalBets: row.TotalBets, TotalBets: row.TotalBets,
TotalCashIn: totalCashIn, TotalCashIn: totalCashIn,
@ -702,7 +702,7 @@ func (s *Service) fetchReportData(ctx context.Context, period string) (domain.Re
totalCashBacks = 0 totalCashBacks = 0
} }
branchReports = append(branchReports, domain.BranchReport{ branchReports = append(branchReports, domain.BranchReport{
BranchID: row.BranchID.Int64, BranchID: row.BranchID,
BranchName: row.BranchName, BranchName: row.BranchName,
CompanyID: row.CompanyID, CompanyID: row.CompanyID,
TotalBets: row.TotalBets, TotalBets: row.TotalBets,

View File

@ -25,7 +25,7 @@ var (
ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit") ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit")
ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached") ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached")
ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit") ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit")
ErrInvalidAmount = errors.New("Invalid amount") ErrInvalidAmount = errors.New("Invalid amount")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid") ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
) )
@ -167,7 +167,6 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
} }
if req.Amount < 1 { if req.Amount < 1 {
return domain.Ticket{}, 0, ErrInvalidAmount return domain.Ticket{}, 0, ErrInvalidAmount
} }
@ -211,7 +210,7 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
// Check to see if the total winning amount is over a set limit // Check to see if the total winning amount is over a set limit
if totalWinnings > settingsList.TotalWinningLimit.Float32() { if totalWinnings > settingsList.TotalWinningLimit.Float32() {
s.mongoLogger.Error("Total Winnings over limit", s.mongoLogger.Info("Total Winnings over limit",
zap.Float32("Total Odds", totalOdds), zap.Float32("Total Odds", totalOdds),
zap.Float32("amount", req.Amount), zap.Float32("amount", req.Amount),
zap.Float32("limit", settingsList.TotalWinningLimit.Float32())) zap.Float32("limit", settingsList.TotalWinningLimit.Float32()))
@ -280,3 +279,24 @@ func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
func (s *Service) DeleteOldTickets(ctx context.Context) error { func (s *Service) DeleteOldTickets(ctx context.Context) error {
return s.ticketStore.DeleteOldTickets(ctx) return s.ticketStore.DeleteOldTickets(ctx)
} }
func (s *Service) CheckTicketError(err error) bool {
ticketError := []error{
ErrTicketHasExpired,
ErrNoEventsAvailable,
ErrEventHasBeenRemoved,
ErrTooManyOutcomesForTicket,
ErrTicketAmountTooHigh,
ErrTicketLimitForSingleUser,
ErrTicketWinningTooHigh,
ErrInvalidAmount,
ErrRawOddInvalid,
}
for _, e := range ticketError {
if errors.Is(err, e) {
return true
}
}
return false
}

View File

@ -50,12 +50,9 @@ func (s *Service) CreateShopBet(ctx context.Context, userID int64, role domain.R
} }
newBet, err := s.betSvc.PlaceBet(ctx, domain.CreateBetReq{ newBet, err := s.betSvc.PlaceBet(ctx, domain.CreateBetReq{
Outcomes: req.Outcomes, Outcomes: req.Outcomes,
Amount: req.Amount, Amount: req.Amount,
FullName: req.FullName, }, userID, role, userCompanyID)
PhoneNumber: req.PhoneNumber,
BranchID: branchID,
}, userID, role)
if err != nil { if err != nil {
return domain.ShopBet{}, err return domain.ShopBet{}, err

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
@ -31,28 +32,32 @@ type CreateAdminReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin [post] // @Router /api/v1/admin [post]
func (h *Handler) CreateAdmin(c *fiber.Ctx) error { func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
var companyID domain.ValidInt64 var companyID domain.ValidInt64
var req CreateAdminReq var req CreateAdminReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("failed to parse CreateAdmin request", h.mongoLoggerSvc.Info("failed to parse CreateAdmin request",
zap.Int64("status_code", fiber.StatusBadRequest), zap.Int64("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid request:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("validation failed for CreateAdmin request", h.mongoLoggerSvc.Error("validation failed for CreateAdmin request",
zap.Int64("status_code", fiber.StatusBadRequest), zap.Int64("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
if req.CompanyID == nil { if req.CompanyID == nil {
@ -69,7 +74,7 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Company ID is invalid", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
} }
companyID = domain.ValidInt64{ companyID = domain.ValidInt64{
Value: *req.CompanyID, Value: *req.CompanyID,
@ -95,13 +100,16 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create admin", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error())
} }
if req.CompanyID != nil { if req.CompanyID != nil {
_, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
ID: *req.CompanyID, ID: *req.CompanyID,
AdminID: &newUser.ID, AdminID: domain.ValidInt64{
Value: newUser.ID,
Valid: true,
},
}) })
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("failed to update company with new admin", h.mongoLoggerSvc.Error("failed to update company with new admin",
@ -111,7 +119,7 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update company"+err.Error())
} }
} }
@ -152,7 +160,7 @@ type AdminRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin [get] // @Router /api/v1/admin [get]
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
searchQuery := c.Query("query") searchQuery := c.Query("query")
@ -166,8 +174,8 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.logger.Info("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} }
createdBefore = domain.ValidTime{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -180,8 +188,8 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.logger.Info("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} }
createdAfter = domain.ValidTime{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -209,12 +217,16 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
h.mongoLoggerSvc.Error("invalid filter values in GetAllAdmins request", var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter) admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
@ -225,7 +237,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Admins", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Admins"+err.Error())
} }
result := make([]AdminRes, len(admins)) result := make([]AdminRes, len(admins))
@ -241,7 +253,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
} }
} }
@ -283,7 +295,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin/{id} [get] // @Router /api/v1/admin/{id} [get]
func (h *Handler) GetAdminByID(c *fiber.Ctx) error { func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
userIDstr := c.Params("id") userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64) userID, err := strconv.ParseInt(userIDstr, 10, 64)
@ -294,7 +306,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid admin ID", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid admin ID")
} }
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
@ -305,7 +317,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get admin", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admin"+err.Error())
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
@ -316,7 +328,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
} }
if err == authentication.ErrRefreshTokenNotFound { if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &user.CreatedAt lastLogin = &user.CreatedAt
@ -365,7 +377,7 @@ type updateAdminReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin/{id} [put] // @Router /api/v1/admin/{id} [put]
func (h *Handler) UpdateAdmin(c *fiber.Ctx) error { func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
var req updateAdminReq var req updateAdminReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -374,29 +386,33 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("UpdateAdmin failed - validation errors", h.mongoLoggerSvc.Error("UpdateAdmin failed - validation errors",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
AdminIDStr := c.Params("id") AdminIDStr := c.Params("id")
AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64) AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed - invalid Admin ID param", h.mongoLoggerSvc.Info("UpdateAdmin failed - invalid Admin ID param",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.String("admin_id_param", AdminIDStr), zap.String("admin_id_param", AdminIDStr),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Admin ID", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID")
} }
var companyID domain.ValidInt64 var companyID domain.ValidInt64
@ -430,13 +446,16 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update admin", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin:"+err.Error())
} }
if req.CompanyID != nil { if req.CompanyID != nil {
_, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
ID: *req.CompanyID, ID: *req.CompanyID,
AdminID: &AdminID, AdminID: domain.ValidInt64{
Value: AdminID,
Valid: true,
},
}) })
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed to update company", h.mongoLoggerSvc.Error("UpdateAdmin failed to update company",
@ -445,7 +464,7 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", nil, nil) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update company:"+err.Error())
} }
} }

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
@ -36,41 +37,47 @@ type loginCustomerRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/login [post] // @Router /api/v1/auth/login [post]
func (h *Handler) LoginCustomer(c *fiber.Ctx) error { func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
var req loginCustomerReq var req loginCustomerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("Failed to parse LoginCustomer request", h.mongoLoggerSvc.Info("Failed to parse LoginCustomer request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
} }
if _, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
h.mongoLoggerSvc.Error("LoginCustomer validation failed", var errMsg string
zap.Int("status_code", fiber.StatusBadRequest), for field, msg := range valErrs {
zap.Any("request", req), errMsg += fmt.Sprintf("%s: %s; ", field, msg)
zap.Time("timestamp", time.Now()), }
) return fiber.NewError(fiber.StatusBadRequest, errMsg)
return fiber.NewError(fiber.StatusBadRequest, "Invalid Request")
} }
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password) successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("Login attempt failed",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
switch { switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound): 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") return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
case errors.Is(err, authentication.ErrUserSuspended): 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") return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
default: default:
h.mongoLoggerSvc.Error("Login failed", h.mongoLoggerSvc.Error("Login failed",
@ -125,7 +132,7 @@ type refreshToken struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/refresh [post] // @Router /api/v1/auth/refresh [post]
func (h *Handler) RefreshToken(c *fiber.Ctx) error { func (h *Handler) RefreshToken(c *fiber.Ctx) error {
type loginCustomerRes struct { type loginCustomerRes struct {
@ -136,35 +143,47 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
var req refreshToken var req refreshToken
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("Failed to parse RefreshToken request", h.mongoLoggerSvc.Info("Failed to parse RefreshToken request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
h.mongoLoggerSvc.Error("RefreshToken validation failed", var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
refreshToken, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken) refreshToken, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("Refresh token attempt failed",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
switch { switch {
case errors.Is(err, authentication.ErrExpiredToken): case errors.Is(err, authentication.ErrExpiredToken):
h.mongoLoggerSvc.Info("Refresh token attempt failed: The refresh token has expired",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired") return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired")
case errors.Is(err, authentication.ErrRefreshTokenNotFound): case errors.Is(err, authentication.ErrRefreshTokenNotFound):
h.mongoLoggerSvc.Info("Refresh token attempt failed: Refresh token not found",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found") return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found")
default: default:
h.mongoLoggerSvc.Error("Refresh token failed", h.mongoLoggerSvc.Error("Refresh token failed",
@ -184,7 +203,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information:"+err.Error())
} }
accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
@ -195,7 +214,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token:"+err.Error())
} }
res := loginCustomerRes{ res := loginCustomerRes{
@ -204,7 +223,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
Role: string(user.Role), Role: string(user.Role),
} }
h.mongoLoggerSvc.Info("Refresh token successful", h.mongoLoggerSvc.Info("Token Refreshed Successfully",
zap.Int("status_code", fiber.StatusOK), zap.Int("status_code", fiber.StatusOK),
zap.Int64("user_id", user.ID), zap.Int64("user_id", user.ID),
zap.String("role", string(user.Role)), zap.String("role", string(user.Role)),
@ -229,39 +248,53 @@ type logoutReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/logout [post] // @Router /api/v1/auth/logout [post]
func (h *Handler) LogOutCustomer(c *fiber.Ctx) error { func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
var req logoutReq var req logoutReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("Failed to parse LogOutCustomer request", h.mongoLoggerSvc.Info("Failed to parse LogOutCustomer request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
h.mongoLoggerSvc.Error("LogOutCustomer validation failed",
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("LogOutCustomer validation failed",
zap.String("errMsg", errMsg),
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err := h.authSvc.Logout(c.Context(), req.RefreshToken) err := h.authSvc.Logout(c.Context(), req.RefreshToken)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("Logout attempt failed",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
switch { switch {
case errors.Is(err, authentication.ErrExpiredToken): case errors.Is(err, authentication.ErrExpiredToken):
h.mongoLoggerSvc.Info("Logout attempt failed:The refresh token has expired",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired") return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired")
case errors.Is(err, authentication.ErrRefreshTokenNotFound): case errors.Is(err, authentication.ErrRefreshTokenNotFound):
h.mongoLoggerSvc.Info("Logout attempt failed: Refresh token not found",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("refresh_token", req.RefreshToken),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found") return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found")
default: default:
h.mongoLoggerSvc.Error("Logout failed", h.mongoLoggerSvc.Error("Logout failed",
@ -269,7 +302,7 @@ func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error") return fiber.NewError(fiber.StatusInternalServerError, "Internal server error"+err.Error())
} }
} }

View File

@ -23,10 +23,11 @@ import (
// @Success 200 {object} domain.BetRes // @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet [post] // @Router /api/v1/sport/bet [post]
func (h *Handler) CreateBet(c *fiber.Ctx) error { func (h *Handler) CreateBet(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
var req domain.CreateBetReq var req domain.CreateBetReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -35,16 +36,18 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
res, err := h.CreateBetInternal(c, req, userID, role) res, err := h.CreateBetInternal(c, req, userID, role, companyID)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to create bet", h.mongoLoggerSvc.Error("Failed to create bet",
zap.Int("status_code", fiber.StatusOK), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.String("role", string(role)),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error())
} }
h.mongoLoggerSvc.Info("Bet created successfully", h.mongoLoggerSvc.Info("Bet created successfully",
@ -66,10 +69,11 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes // @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet/fastcode [post] // @Router /api/v1/sport/bet/fastcode [post]
func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
var req struct { var req struct {
FastCode string `json:"fast_code"` FastCode string `json:"fast_code"`
@ -81,27 +85,29 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
bet, err := h.betSvc.GetBetByFastCode(c.Context(), req.FastCode) bet, err := h.betSvc.GetBetByFastCode(c.Context(), req.FastCode)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("falied to get bet with fast code", h.mongoLoggerSvc.Info("failed to get bet with fast code",
zap.Int("status_code", fiber.StatusInternalServerError), zap.String("fast_code", req.FastCode),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "falied to get bet with fast code") return fiber.NewError(fiber.StatusBadRequest, "failed to get bet with fast code:"+err.Error())
} }
outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID) outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("falied to get BetOutcomes by BetID", h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID",
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("bet_id", bet.ID),
zap.Error(err), zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
zap.Error(err),
) )
return fiber.NewError(fiber.StatusBadRequest, "falied to get BetOutcomes by BetID") return fiber.NewError(fiber.StatusBadRequest, "failed to get BetOutcomes by BetID:"+err.Error())
} }
bet_outcomes := []domain.CreateBetOutcomeReq{} bet_outcomes := []domain.CreateBetOutcomeReq{}
@ -113,58 +119,43 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
}) })
} }
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.mongoLoggerSvc.Error("falied to get user information",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "falied to get user information")
}
// branch, err := h.branchSvc.GetBranchByID(c.Context(), user)
// if err != nil {
// h.mongoLoggerSvc.Error("falied to get branch of user",
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusBadRequest, "falied to get branch of user")
// }
newReq := domain.CreateBetReq{ newReq := domain.CreateBetReq{
Amount: float32(bet.Amount), Amount: float32(bet.Amount),
Outcomes: bet_outcomes, Outcomes: bet_outcomes,
BranchID: nil, BranchID: nil,
FullName: user.FirstName,
PhoneNumber: user.PhoneNumber,
} }
res, err := h.CreateBetInternal(c, newReq, userID, role) res, err := h.CreateBetInternal(c, newReq, userID, role, companyID)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to create bet", h.mongoLoggerSvc.Error("Failed to create bet",
zap.Int("status_code", fiber.StatusOK), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.String("role", string(role)),
zap.Any("newReq", newReq),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
zap.Error(err),
) )
return fiber.NewError(fiber.StatusBadRequest, "Failed to create bet") return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error())
} }
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), bet.UserID.Value) wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), bet.UserID)
// amount added for fast code owner can be fetched from settings in db // amount added for fast code owner can be fetched from settings in db
amount := domain.Currency(100) settingList, err := h.settingSvc.GetSettingList(c.Context())
amount := settingList.AmountForBetReferral
_, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, amount, domain.ValidInt64{}, _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, amount, domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to static wallet by referring using fast_code", amount.Float32())) domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to static wallet by referring using fast_code", amount.Float32()))
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to add reward to static bet", h.mongoLoggerSvc.Error("Failed to add reward to static bet",
zap.Int("status_code", fiber.StatusOK), zap.Int("status_code", fiber.StatusBadRequest),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.Float32("amount", amount.Float32()),
zap.Int64("static wallet_id", wallet.StaticID),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
zap.Error(err),
) )
return fiber.NewError(fiber.StatusBadRequest, "Failed to add reward to static bet") return fiber.NewError(fiber.StatusBadRequest, "Failed to add reward to static bet:"+err.Error())
} }
h.mongoLoggerSvc.Info("Bet created successfully", h.mongoLoggerSvc.Info("Bet created successfully",
@ -175,7 +166,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
} }
func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) { func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userID int64, role domain.Role, companyID domain.ValidInt64) (domain.CreateBetRes, error) {
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
h.mongoLoggerSvc.Error("CreateBet validation failed", h.mongoLoggerSvc.Error("CreateBet validation failed",
@ -186,19 +177,30 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
return domain.CreateBetRes{}, fmt.Errorf("%s", valErrs) return domain.CreateBetRes{}, fmt.Errorf("%s", valErrs)
} }
res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role) res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role, companyID)
if err != nil { if err != nil {
switch err {
case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient:
h.mongoLoggerSvc.Info("PlaceBet failed",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Int64("userID", userID),
zap.Int64("companyID", companyID.Value),
zap.String("role", string(role)),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
}
h.mongoLoggerSvc.Error("PlaceBet failed", h.mongoLoggerSvc.Error("PlaceBet failed",
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("userID", userID),
zap.Int64("companyID", companyID.Value),
zap.String("role", string(role)),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
switch err {
case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient:
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet") return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet")
} }
@ -215,28 +217,28 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
// @Success 200 {object} domain.BetRes // @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/random/bet [post] // @Router /api/v1/sport/random/bet [post]
func (h *Handler) RandomBet(c *fiber.Ctx) error { func (h *Handler) RandomBet(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
leagueIDQuery, err := strconv.Atoi(c.Query("league_id")) leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("invalid league id", h.mongoLoggerSvc.Info("invalid league id",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
} }
sportIDQuery, err := strconv.Atoi(c.Query("sport_id")) sportIDQuery, err := strconv.Atoi(c.Query("sport_id"))
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("invalid sport id", h.mongoLoggerSvc.Info("invalid sport id",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
} }
firstStartTimeQuery := c.Query("first_start_time") firstStartTimeQuery := c.Query("first_start_time")
@ -255,12 +257,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
if firstStartTimeQuery != "" { if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("invalid start_time format", h.mongoLoggerSvc.Info("invalid first_start_time format",
zap.String("first_start_time", firstStartTimeQuery),
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid first_start_time format")
} }
firstStartTime = domain.ValidTime{ firstStartTime = domain.ValidTime{
Value: firstStartTimeParsed, Value: firstStartTimeParsed,
@ -272,12 +275,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
if lastStartTimeQuery != "" { if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("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.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Invalid last_start_time format")
} }
lastStartTime = domain.ValidTime{ lastStartTime = domain.ValidTime{
Value: lastStartTimeParsed, Value: lastStartTimeParsed,
@ -287,38 +291,45 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
var req domain.RandomBetReq var req domain.RandomBetReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("Failed to parse RandomBet request", h.mongoLoggerSvc.Info("Failed to parse RandomBet request",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("RandomBet validation failed", h.mongoLoggerSvc.Error("RandomBet validation failed",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
var res domain.CreateBetRes var res domain.CreateBetRes
for i := 0; i < int(req.NumberOfBets); i++ { for i := 0; i < int(req.NumberOfBets); i++ {
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime) res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Random Bet failed", switch err {
case bet.ErrNoEventsAvailable:
return fiber.NewError(fiber.StatusNotFound, "No events found")
}
h.mongoLoggerSvc.Error("Random Bet failed place random bet",
zap.Int64("userID", userID),
zap.Int64("branch_id", req.BranchID),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
switch err { return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet:"+err.Error())
case bet.ErrNoEventsAvailable:
return fiber.NewError(fiber.StatusBadRequest, "No events found")
}
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
} }
} }
@ -340,23 +351,24 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Success 200 {array} domain.BetRes // @Success 200 {array} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet [get] // @Router /api/v1/sport/bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error { func (h *Handler) GetAllBet(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64) // branchID := c.Locals("branch_id").(domain.ValidInt64)
var isShopBet domain.ValidBool var isShopBet domain.ValidBool
isShopBetQuery := c.Query("is_shop") isShopBetQuery := c.Query("is_shop")
if isShopBetQuery != "" && role == domain.RoleSuperAdmin { if isShopBetQuery != "" && role == domain.RoleSuperAdmin {
isShopBetParse, err := strconv.ParseBool(isShopBetQuery) isShopBetParse, err := strconv.ParseBool(isShopBetQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to parse is_shop_bet", h.mongoLoggerSvc.Info("failed to parse is_shop_bet",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.String("is_shop", isShopBetQuery),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet") return fiber.NewError(fiber.StatusBadRequest, "failed to parse is_shop_bet")
} }
isShopBet = domain.ValidBool{ isShopBet = domain.ValidBool{
Value: isShopBetParse, Value: isShopBetParse,
@ -375,8 +387,13 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_before format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -389,8 +406,13 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_after format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -398,24 +420,19 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
} }
} }
fmt.Printf("Filters - BranchID: %+v, CompanyID: %+v, IsShopBet: %+v, Query: %+v, CreatedBefore: %+v, CreatedAfter: %+v\n",
branchID, companyID, isShopBet, searchString, createdBefore, createdAfter)
bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
BranchID: branchID,
CompanyID: companyID,
IsShopBet: isShopBet, IsShopBet: isShopBet,
Query: searchString, Query: searchString,
CreatedBefore: createdBefore, CreatedBefore: createdBefore,
CreatedAfter: createdAfter, CreatedAfter: createdAfter,
}) })
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get bets", h.mongoLoggerSvc.Error("Failed to get all bets",
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets"+err.Error())
} }
res := make([]domain.BetRes, len(bets)) res := make([]domain.BetRes, len(bets))
@ -423,11 +440,6 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
res[i] = domain.ConvertBet(bet) res[i] = domain.ConvertBet(bet)
} }
h.mongoLoggerSvc.Info("All bets retrieved successfully",
zap.Int("status_code", fiber.StatusOK),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
} }
@ -441,12 +453,12 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes // @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet/{id} [get] // @Router /api/v1/sport/bet/{id} [get]
func (h *Handler) GetBetByID(c *fiber.Ctx) error { func (h *Handler) GetBetByID(c *fiber.Ctx) error {
betID := c.Params("id") betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64) id, err := strconv.ParseInt(betID, 10, 64)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Invalid bet ID", h.mongoLoggerSvc.Info("Invalid bet ID",
zap.String("betID", betID), zap.String("betID", betID),
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
@ -457,7 +469,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
bet, err := h.betSvc.GetBetByID(c.Context(), id) bet, err := h.betSvc.GetBetByID(c.Context(), id)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get bet by ID", h.mongoLoggerSvc.Info("Failed to get bet by ID",
zap.Int64("betID", id), zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusNotFound), zap.Int("status_code", fiber.StatusNotFound),
zap.Error(err), zap.Error(err),
@ -468,47 +480,11 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
res := domain.ConvertBet(bet) res := domain.ConvertBet(bet)
h.mongoLoggerSvc.Info("Bet retrieved successfully", // h.mongoLoggerSvc.Info("Bet retrieved successfully",
zap.Int64("betID", id), // zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusOK), // zap.Int("status_code", fiber.StatusOK),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
// GetBetByCashoutID godoc
// @Summary Gets bet by cashout id
// @Description Gets a single bet by cashout id
// @Tags bet
// @Accept json
// @Produce json
// @Param id path string true "cashout ID"
// @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /sport/bet/cashout/{id} [get]
func (h *Handler) GetBetByCashoutID(c *fiber.Ctx) error {
cashoutID := c.Params("id")
bet, err := h.betSvc.GetBetByCashoutID(c.Context(), cashoutID)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get bet by cashout ID",
zap.String("cashoutID", cashoutID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
}
res := domain.ConvertBet(bet)
h.mongoLoggerSvc.Info("Bet retrieved successfully by cashout ID",
zap.String("cashoutID", cashoutID),
zap.Int("status_code", fiber.StatusOK),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
} }
@ -528,7 +504,7 @@ type UpdateCashOutReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet/{id} [patch] // @Router /api/v1/sport/bet/{id} [patch]
func (h *Handler) UpdateCashOut(c *fiber.Ctx) error { func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
type UpdateCashOutReq struct { type UpdateCashOutReq struct {
CashedOut bool `json:"cashed_out" validate:"required" example:"true"` CashedOut bool `json:"cashed_out" validate:"required" example:"true"`
@ -554,11 +530,15 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request body", err, nil) return fiber.NewError(fiber.StatusBadRequest, "failed to parse request body:"+err.Error())
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut) err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut)
@ -591,7 +571,7 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /sport/bet/{id} [delete] // @Router /api/v1/sport/bet/{id} [delete]
func (h *Handler) DeleteBet(c *fiber.Ctx) error { func (h *Handler) DeleteBet(c *fiber.Ctx) error {
betID := c.Params("id") betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64) id, err := strconv.ParseInt(betID, 10, 64)
@ -613,7 +593,7 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error {
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet") return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet:"+err.Error())
} }
h.mongoLoggerSvc.Info("Bet removed successfully", h.mongoLoggerSvc.Info("Bet removed successfully",

View File

@ -1,8 +1,11 @@
package handlers package handlers
import ( import (
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error { func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
@ -12,8 +15,13 @@ func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("failed to parse bonus multiplier", "error", err) h.logger.Error("failed to parse bonus multiplier request", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
// currently only one multiplier is allowed // currently only one multiplier is allowed
@ -21,29 +29,42 @@ func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context()) multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
if err != nil { if err != nil {
h.logger.Error("failed to get bonus multiplier", "error", err) h.logger.Error("failed to get bonus multiplier", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) h.mongoLoggerSvc.Info("Failed to get bonus multiplier",
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 len(multipliers) > 0 { if len(multipliers) > 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) return fiber.NewError(fiber.StatusBadRequest, "only one multiplier is allowed")
} }
if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier, req.BalanceCap); err != nil { if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier, req.BalanceCap); err != nil {
h.logger.Error("failed to create bonus multiplier", "error", err) h.mongoLoggerSvc.Error("failed to create bonus multiplier",
return response.WriteJSON(c, fiber.StatusInternalServerError, "failed to create bonus mulitplier", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "failed to create bonus multiplier"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Create bonus mulitplier successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Create bonus multiplier successfully", nil, nil)
} }
func (h *Handler) GetBonusMultiplier(c *fiber.Ctx) error { func (h *Handler) GetBonusMultiplier(c *fiber.Ctx) error {
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context()) multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
if err != nil { if err != nil {
h.logger.Error("failed to get bonus multiplier", "error", err) h.mongoLoggerSvc.Info("failed to get bonus multiplier",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Fetched bonus mulitplier successfully", multipliers, nil) return response.WriteJSON(c, fiber.StatusOK, "Fetched bonus multiplier successfully", multipliers, nil)
} }
func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error { func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error {
@ -54,14 +75,24 @@ func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error {
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("failed to parse bonus multiplier", "error", err) h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) 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 err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier, req.BalanceCap); err != nil { if err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier, req.BalanceCap); err != nil {
h.logger.Error("failed to update bonus multiplier", "error", err) h.logger.Error("failed to update bonus multiplier", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "failed to update bonus mulitplier", nil, nil) h.mongoLoggerSvc.Error("failed to update bonus multiplier",
zap.Int64("id", req.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "failed to update bonus multiplier:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Updated bonus mulitplier successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Updated bonus multiplier successfully", nil, nil)
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CreateCashierReq struct { type CreateCashierReq struct {
@ -32,7 +33,7 @@ type CreateCashierReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashiers [post] // @Router /api/v1/cashiers [post]
func (h *Handler) CreateCashier(c *fiber.Ctx) error { func (h *Handler) CreateCashier(c *fiber.Ctx) error {
// Get user_id from middleware // Get user_id from middleware
@ -40,19 +41,33 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
var req CreateCashierReq var req CreateCashierReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err) h.mongoLoggerSvc.Info("failed to parse CreateCashier request body",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate CreateCashier",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
// Cashiers inherit the company id from the branch id // Cashiers inherit the company id from the branch id
// TODO add a check here to make sure that the admin/manager if from same company
branch, err := h.branchSvc.GetBranchByID(c.Context(), req.BranchID) branch, err := h.branchSvc.GetBranchByID(c.Context(), req.BranchID)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is invalid", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Branch ID is invalid")
} }
userRequest := domain.CreateUserReq{ userRequest := domain.CreateUserReq{
FirstName: req.FirstName, FirstName: req.FirstName,
@ -70,14 +85,25 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
fmt.Print(req.Suspended) fmt.Print(req.Suspended)
newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true) newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true)
if err != nil { if err != nil {
h.logger.Error("CreateCashier failed", "error", err) h.mongoLoggerSvc.Error("Failed to create cashier user",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) zap.Any("userRequest", userRequest),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create cashier user:"+err.Error())
} }
err = h.branchSvc.CreateBranchCashier(c.Context(), req.BranchID, newUser.ID) err = h.branchSvc.CreateBranchCashier(c.Context(), req.BranchID, newUser.ID)
if err != nil { if err != nil {
h.logger.Error("CreateCashier failed", "error", err) h.mongoLoggerSvc.Error("failed to create branch cashier",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) zap.Int64("branchID", req.BranchID),
zap.Int64("userID", newUser.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create branch cashier:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil)
@ -111,17 +137,22 @@ type GetCashierRes struct {
// @Produce json // @Produce json
// @Param page query int false "Page number" // @Param page query int false "Page number"
// @Param page_size query int false "Page size" // @Param page_size query int false "Page size"
// @Success 200 {object} response.APIResponse // @Success 200 {array} GetCashierRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashiers [get] // @Router /api/v1/cashiers [get]
func (h *Handler) GetAllCashiers(c *fiber.Ctx) error { func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) companyId := c.Locals("company_id").(domain.ValidInt64)
if role != domain.RoleSuperAdmin && !companyId.Valid { if role != domain.RoleSuperAdmin && !companyId.Valid {
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") h.mongoLoggerSvc.Error("Cannot get company ID in context",
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context")
} }
searchQuery := c.Query("query") searchQuery := c.Query("query")
searchString := domain.ValidString{ searchString := domain.ValidString{
@ -134,8 +165,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_before format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdBefore", 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -148,8 +184,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_after format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -174,7 +215,16 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate filters for GetAllCashier",
zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
cashiers, total, err := h.userSvc.GetAllCashiers(c.Context(), domain.UserFilter{ cashiers, total, err := h.userSvc.GetAllCashiers(c.Context(), domain.UserFilter{
@ -183,8 +233,12 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
CreatedAfter: createdAfter, CreatedAfter: createdAfter,
}) })
if err != nil { if err != nil {
h.logger.Error("GetAllCashiers failed", "error", err) h.mongoLoggerSvc.Error("failed to get all cashiers",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get cashiers:"+err.Error())
} }
var result []GetCashierRes = make([]GetCashierRes, 0, len(cashiers)) var result []GetCashierRes = make([]GetCashierRes, 0, len(cashiers))
@ -195,8 +249,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if err == authentication.ErrRefreshTokenNotFound { if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &cashier.CreatedAt lastLogin = &cashier.CreatedAt
} else { } else {
h.logger.Error("Failed to get user last login", "userID", cashier.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Int64("userID", cashier.ID),
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())
} }
} }
@ -236,7 +295,7 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashier/{id} [get] // @Router /api/v1/cashier/{id} [get]
func (h *Handler) GetCashierByID(c *fiber.Ctx) error { func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
// branchId := int64(12) //c.Locals("branch_id").(int64) // branchId := int64(12) //c.Locals("branch_id").(int64)
// filter := user.Filter{ // filter := user.Filter{
@ -256,22 +315,37 @@ func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
stringID := c.Params("id") stringID := c.Params("id")
cashierID, err := strconv.ParseInt(stringID, 10, 64) cashierID, err := strconv.ParseInt(stringID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("failed to fetch user using UserID", "error", err) h.mongoLoggerSvc.Info("failed to parse user_id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) zap.String("stringID", stringID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid cashier ID")
} }
user, err := h.userSvc.GetCashierByID(c.Context(), cashierID) user, err := h.userSvc.GetCashierByID(c.Context(), cashierID)
if err != nil { if err != nil {
h.logger.Error("Get User By ID failed", "error", err) h.mongoLoggerSvc.Error("Get User By ID failed",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get cashiers:"+err.Error())
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "cashierID", user.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Int64("userID", user.ID),
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 lastLogin = &user.CreatedAt
} }
@ -316,24 +390,42 @@ type updateCashierReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashiers/{id} [put] // @Router /api/v1/cashiers/{id} [put]
func (h *Handler) UpdateCashier(c *fiber.Ctx) error { func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
cashierIdStr := c.Params("id") cashierIdStr := c.Params("id")
cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64) cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("UpdateCashier failed", "error", err) h.mongoLoggerSvc.Info("UpdateCashier invalid cashier ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid cashier ID")
} }
var req updateCashierReq var req updateCashierReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateCashier failed", "error", err) h.mongoLoggerSvc.Info("UpdateCashier failed to parse request body",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate update cashier request",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
@ -353,8 +445,14 @@ func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
}, },
) )
if err != nil { if err != nil {
h.logger.Error("UpdateCashier failed", "error", err) h.mongoLoggerSvc.Error("failed to update cashier",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cashier", nil, nil) zap.Int64("userID", cashierId),
zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update cashier"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil)

View File

@ -0,0 +1 @@
package handlers

View File

@ -1,92 +1,60 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CreateCompanyReq struct {
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
}
type UpdateCompanyReq struct {
Name *string `json:"name,omitempty" example:"CompanyName"`
AdminID *int64 `json:"admin_id,omitempty" example:"1"`
}
type CompanyRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
WalletID int64 `json:"wallet_id" example:"1"`
}
type GetCompanyRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"CompanyName"`
AdminID int64 `json:"admin_id" example:"1"`
WalletID int64 `json:"wallet_id" example:"1"`
WalletBalance float32 `json:"balance" example:"1"`
IsActive bool `json:"is_active" example:"false"`
AdminFirstName string `json:"admin_first_name" example:"John"`
AdminLastName string `json:"admin_last_name" example:"Doe"`
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
}
func convertCompany(company domain.Company) CompanyRes {
return CompanyRes{
ID: company.ID,
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
}
}
func convertGetCompany(company domain.GetCompany) GetCompanyRes {
return GetCompanyRes{
ID: company.ID,
Name: company.Name,
AdminID: company.AdminID,
WalletID: company.WalletID,
WalletBalance: company.WalletBalance.Float32(),
IsActive: company.IsWalletActive,
AdminFirstName: company.AdminFirstName,
AdminLastName: company.AdminLastName,
AdminPhoneNumber: company.AdminPhoneNumber,
}
}
// CreateCompany godoc // CreateCompany godoc
// @Summary Create a company // @Summary Create a company
// @Description Creates a company // @Description Creates a company
// @Tags company // @Tags company
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param createCompany body CreateCompanyReq true "Creates company" // @Param createCompany body domain.CreateCompanyReq true "Creates company"
// @Success 200 {object} CompanyRes // @Success 200 {object} domain.CompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /company [post] // @Router /api/v1/company [post]
func (h *Handler) CreateCompany(c *fiber.Ctx) error { func (h *Handler) CreateCompany(c *fiber.Ctx) error {
var req CreateCompanyReq var req domain.CreateCompanyReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err) h.mongoLoggerSvc.Info("CreateCompanyReq failed",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate create company",
zap.String("errMsg", errMsg),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
user, err := h.userSvc.GetUserByID(c.Context(), req.AdminID) user, err := h.userSvc.GetUserByID(c.Context(), req.AdminID)
if err != nil { if err != nil {
h.logger.Error("Error fetching user", "error", err) h.mongoLoggerSvc.Error("Error fetching user",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get user", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
// Create Company Wallet // Create Company Wallet
@ -98,8 +66,13 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
}) })
if err != nil { if err != nil {
h.logger.Error("Create Company Wallet failed", "error", err) h.mongoLoggerSvc.Error("Create Company Wallet failed",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create company wallet", err, nil) zap.Int64("admin", req.AdminID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
company, err := h.companySvc.CreateCompany(c.Context(), domain.CreateCompany{ company, err := h.companySvc.CreateCompany(c.Context(), domain.CreateCompany{
@ -109,20 +82,29 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
}) })
if err != nil { if err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err) h.mongoLoggerSvc.Error("CreateCompanyReq failed to create company",
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ zap.Int64("userID", user.ID),
"error": "Internal server error", zap.String("name", req.Name),
}) zap.String("Name", req.Name),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
err = h.userSvc.UpdateUserCompany(c.Context(), user.ID, company.ID) err = h.userSvc.UpdateUserCompany(c.Context(), user.ID, company.ID)
if err != nil { if err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err) h.mongoLoggerSvc.Error("Failed to update user company",
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ zap.Int64("userID", user.ID),
"error": "Internal server error", zap.Int64("companyID", company.ID),
}) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertCompany(company) res := domain.ConvertCompany(company)
return response.WriteJSON(c, fiber.StatusCreated, "Company Created", res, nil) return response.WriteJSON(c, fiber.StatusCreated, "Company Created", res, nil)
} }
@ -133,10 +115,10 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
// @Tags company // @Tags company
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {array} CompanyRes // @Success 200 {array} domain.GetCompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /company [get] // @Router /api/v1/company [get]
func (h *Handler) GetAllCompanies(c *fiber.Ctx) error { func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
searchQuery := c.Query("query") searchQuery := c.Query("query")
searchString := domain.ValidString{ searchString := domain.ValidString{
@ -149,8 +131,13 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_before format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdBeforeQuery", 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -163,8 +150,13 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_after format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -178,14 +170,18 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
CreatedAfter: createdAfter, CreatedAfter: createdAfter,
}) })
if err != nil { if err != nil {
h.logger.Error("Failed to get companies", "error", err) h.mongoLoggerSvc.Error("Failed to get companies",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
var result []GetCompanyRes = make([]GetCompanyRes, 0, len(companies)) var result []domain.GetCompanyRes = make([]domain.GetCompanyRes, 0, len(companies))
for _, company := range companies { for _, company := range companies {
result = append(result, convertGetCompany(company)) result = append(result, domain.ConvertGetCompany(company))
} }
return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil) return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil)
@ -199,27 +195,37 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Company ID" // @Param id path int true "Company ID"
// @Success 200 {object} CompanyRes // @Success 200 {object} domain.GetCompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /company/{id} [get] // @Router /api/v1/company/{id} [get]
func (h *Handler) GetCompanyByID(c *fiber.Ctx) error { func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
companyID := c.Params("id") companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64) id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid company ID", "companyID", companyID, "error", err) h.mongoLoggerSvc.Info("Invalid company ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) zap.String("companyID", companyID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid company ID")
} }
company, err := h.companySvc.GetCompanyByID(c.Context(), id) company, err := h.companySvc.GetCompanyByID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get company by ID", "companyID", id, "error", err) h.mongoLoggerSvc.Error("Failed to get company by ID",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) zap.Int64("companyID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertGetCompany(company) res := domain.ConvertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil)
} }
@ -230,24 +236,33 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
// @Tags company // @Tags company
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} CompanyRes // @Success 200 {object} domain.GetCompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin-company [get] // @Router /api/v1/admin-company [get]
func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error { func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !companyID.Valid {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid company ID", nil, nil) h.mongoLoggerSvc.Error("Invalid company ID",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid company ID")
} }
company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value) company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value)
if err != nil { if err != nil {
h.logger.Error("Failed to get company by ID", "companyID", companyID.Value, "error", err) h.mongoLoggerSvc.Error("Failed to get company by ID",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) zap.Int64("companyID", companyID.Value),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertGetCompany(company) res := domain.ConvertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil)
} }
@ -258,25 +273,34 @@ func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error {
// @Tags company // @Tags company
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {array} CompanyRes // @Success 200 {array} domain.CompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /search/company [get] // @Router /api/v1/search/company [get]
func (h *Handler) SearchCompany(c *fiber.Ctx) error { func (h *Handler) SearchCompany(c *fiber.Ctx) error {
searchQuery := c.Query("q") searchQuery := c.Query("q")
if searchQuery == "" { if searchQuery == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Search query is required", nil, nil) h.mongoLoggerSvc.Info("Search query is required",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Search query is required")
} }
companies, err := h.companySvc.SearchCompanyByName(c.Context(), searchQuery) companies, err := h.companySvc.SearchCompanyByName(c.Context(), searchQuery)
if err != nil { if err != nil {
h.logger.Error("Failed to get companies", "error", err) h.mongoLoggerSvc.Info("Failed to get companies",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) zap.String("search query", searchQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
var result []GetCompanyRes = make([]GetCompanyRes, 0, len(companies)) var result []domain.GetCompanyRes = make([]domain.GetCompanyRes, 0, len(companies))
for _, company := range companies { for _, company := range companies {
result = append(result, convertGetCompany(company)) result = append(result, domain.ConvertGetCompany(company))
} }
return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil) return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil)
@ -290,42 +314,63 @@ func (h *Handler) SearchCompany(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Company ID" // @Param id path int true "Company ID"
// @Param updateCompany body UpdateCompanyReq true "Update Company" // @Param updateCompany body domain.UpdateCompanyReq true "Update Company"
// @Success 200 {object} CompanyRes // @Success 200 {object} domain.CompanyRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /company/{id} [put] // @Router /api/v1/company/{id} [put]
func (h *Handler) UpdateCompany(c *fiber.Ctx) error { func (h *Handler) UpdateCompany(c *fiber.Ctx) error {
companyID := c.Params("id") companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64) id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid company ID", "companyID", companyID, "error", err) h.mongoLoggerSvc.Info("Invalid company ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) zap.String("companyID", companyID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid company ID")
} }
var req UpdateCompanyReq var req domain.UpdateCompanyReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateCompanyReq failed", "error", err) h.mongoLoggerSvc.Info("UpdateCompanyReq failed",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.String("companyID", companyID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("UpdateCompanyReq failed to validate",
zap.String("companyID", companyID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
company, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ company, err := h.companySvc.UpdateCompany(c.Context(), domain.ConvertUpdateCompanyReq(req))
ID: id,
Name: req.Name,
AdminID: req.AdminID,
})
if err != nil { if err != nil {
h.logger.Error("Failed to update company", "companyID", id, "error", err) h.mongoLoggerSvc.Error("Failed to update company",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", err, nil) zap.Int64("companyID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertCompany(company) res := domain.ConvertCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company Updated", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Company Updated", res, nil)
@ -341,21 +386,31 @@ func (h *Handler) UpdateCompany(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /company/{id} [delete] // @Router /api/v1/company/{id} [delete]
func (h *Handler) DeleteCompany(c *fiber.Ctx) error { func (h *Handler) DeleteCompany(c *fiber.Ctx) error {
companyID := c.Params("id") companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64) id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid Company ID", "companyID", companyID, "error", err) h.mongoLoggerSvc.Info("Invalid Company ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Company ID", err, nil) zap.String("companyID", companyID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid Company ID")
} }
err = h.companySvc.DeleteCompany(c.Context(), id) err = h.companySvc.DeleteCompany(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to delete by ID", "Company ID", id, "error", err) h.mongoLoggerSvc.Info("Failed to delete by ID",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Company", err, nil) zap.Int64("Company ID", id),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Company removed successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Company removed successfully", nil, nil)

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
@ -8,9 +9,9 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CustomersRes struct { type CustomersRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
@ -26,6 +27,7 @@ type CustomersRes struct {
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
} }
// GetAllCustomers godoc // GetAllCustomers godoc
// @Summary Get all Customers // @Summary Get all Customers
// @Description Get all Customers // @Description Get all Customers
@ -38,13 +40,18 @@ type CustomersRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /customer [get] // @Router /api/v1/customer [get]
func (h *Handler) GetAllCustomers(c *fiber.Ctx) error { func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) companyId := c.Locals("company_id").(domain.ValidInt64)
// Checking to make sure that admin user has a company id in the token // Checking to make sure that admin user has a company id in the token
if role != domain.RoleSuperAdmin && !companyId.Valid { if role != domain.RoleSuperAdmin && !companyId.Valid {
h.mongoLoggerSvc.Error("Cannot get company ID from context",
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID")
} }
@ -59,8 +66,13 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_before format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdBefore", 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -73,8 +85,13 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_after format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdAfter", 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -99,12 +116,27 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
} }
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate GetAllCustomer filters",
zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
customers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) customers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil { if err != nil {
h.logger.Error("GetAllCustomers failed", "error", err) h.mongoLoggerSvc.Error("GetAllCustomers failed to get all users",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Customers", err, nil) zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Customers:"+err.Error())
} }
var result []CustomersRes = make([]CustomersRes, len(customers)) var result []CustomersRes = make([]CustomersRes, len(customers))
@ -114,8 +146,14 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
if err == authentication.ErrRefreshTokenNotFound { if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &customer.CreatedAt lastLogin = &customer.CreatedAt
} else { } else {
h.logger.Error("Failed to get user last login", "userID", customer.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Int64("userID", customer.ID),
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())
} }
} }
result[index] = CustomersRes{ result[index] = CustomersRes{
@ -150,7 +188,7 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /customer/{id} [get] // @Router /api/v1/customer/{id} [get]
func (h *Handler) GetCustomerByID(c *fiber.Ctx) error { func (h *Handler) GetCustomerByID(c *fiber.Ctx) error {
userIDstr := c.Params("id") userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64) userID, err := strconv.ParseInt(userIDstr, 10, 64)
@ -160,14 +198,25 @@ func (h *Handler) GetCustomerByID(c *fiber.Ctx) error {
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get customers") h.mongoLoggerSvc.Error("Failed to get customers",
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 get customers:"+err.Error())
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve 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 lastLogin = &user.CreatedAt
@ -210,26 +259,45 @@ type updateCustomerReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /customer/{id} [put] // @Router /api/v1/customer/{id} [put]
func (h *Handler) UpdateCustomer(c *fiber.Ctx) error { func (h *Handler) UpdateCustomer(c *fiber.Ctx) error {
var req updateCustomerReq var req updateCustomerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateCustomers failed", "error", err) h.mongoLoggerSvc.Error("UpdateCustomers invalid request body",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate UpdateCustomerReq",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("ErrMsg", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
CustomersIdStr := c.Params("id") CustomersIdStr := c.Params("id")
CustomersId, err := strconv.ParseInt(CustomersIdStr, 10, 64) CustomersId, err := strconv.ParseInt(CustomersIdStr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("UpdateCustomers failed", "error", err) h.mongoLoggerSvc.Info("Invalid Customers ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Customers ID", nil, nil) zap.String("userID", CustomersIdStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid Customers ID")
} }
// var companyID domain.ValidInt64 // var companyID domain.ValidInt64
@ -262,8 +330,13 @@ func (h *Handler) UpdateCustomer(c *fiber.Ctx) error {
}, },
) )
if err != nil { if err != nil {
h.logger.Error("UpdateCustomers failed", "error", err) h.mongoLoggerSvc.Error("Failed to update Customers",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Customers", nil, nil) zap.Int64("userID", CustomersId),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update Customers:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Customers updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Customers updated successfully", nil, nil)

View File

@ -8,6 +8,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// @Summary Retrieve all upcoming events // @Summary Retrieve all upcoming events
@ -24,7 +25,7 @@ import (
// @Param last_start_time query string false "End Time" // @Param last_start_time query string false "End Time"
// @Success 200 {array} domain.UpcomingEvent // @Success 200 {array} domain.UpcomingEvent
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /events [get] // @Router /api/v1/events [get]
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1) page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10) pageSize := c.QueryInt("page_size", 10)
@ -42,8 +43,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if leagueIDQuery != "" { if leagueIDQuery != "" {
leagueIDInt, err := strconv.Atoi(leagueIDQuery) leagueIDInt, err := strconv.Atoi(leagueIDQuery)
if err != nil { if err != nil {
h.logger.Error("invalid league id", "error", err) h.mongoLoggerSvc.Error("invalid league id",
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) zap.String("league_id", leagueIDQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
} }
leagueID = domain.ValidInt32{ leagueID = domain.ValidInt32{
Value: int32(leagueIDInt), Value: int32(leagueIDInt),
@ -55,8 +61,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if sportIDQuery != "" { if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery) sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil { if err != nil {
h.logger.Error("invalid sport id", "error", err) h.mongoLoggerSvc.Info("invalid sport id",
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) zap.String("sportID", sportIDQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
} }
sportID = domain.ValidInt32{ sportID = domain.ValidInt32{
Value: int32(sportIDint), Value: int32(sportIDint),
@ -68,8 +79,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if firstStartTimeQuery != "" { if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid start_time format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("first_start_time", firstStartTimeQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} }
firstStartTime = domain.ValidTime{ firstStartTime = domain.ValidTime{
Value: firstStartTimeParsed, Value: firstStartTimeParsed,
@ -82,8 +98,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if lastStartTimeQuery != "" { if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid start_time format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("last_start_time", lastStartTimeQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} }
lastStartTime = domain.ValidTime{ lastStartTime = domain.ValidTime{
Value: lastStartTimeParsed, Value: lastStartTimeParsed,
@ -97,9 +118,25 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: countryCodeQuery != "", Valid: countryCodeQuery != "",
} }
flaggedQuery := c.Query("flagged") flaggedQuery := c.Query("flagged")
if flaggedQuery != "" && var flagged domain.ValidBool
if flaggedQuery != "" {
flaggedParsed, err := strconv.ParseBool(flaggedQuery)
if err != nil {
h.mongoLoggerSvc.Error("Failed to parse flagged",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
}
flagged = domain.ValidBool{
Value: flaggedParsed,
Valid: true,
}
}
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents( events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(
c.Context(), domain.EventFilter{ c.Context(), domain.EventFilter{
SportID: sportID, SportID: sportID,
@ -109,12 +146,17 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Limit: limit, Limit: limit,
Offset: offset, Offset: offset,
CountryCode: countryCode, CountryCode: countryCode,
Flagged: flagged,
}) })
// fmt.Printf("League ID: %v", leagueID) // fmt.Printf("League ID: %v", leagueID)
if err != nil { if err != nil {
h.logger.Error("getting error", "error", err) h.mongoLoggerSvc.Error("Failed to retrieve all upcoming events",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil, page, int(total)) return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil, page, int(total))
@ -141,14 +183,18 @@ type TopLeague struct {
// @Produce json // @Produce json
// @Success 200 {array} TopLeague // @Success 200 {array} TopLeague
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /top-leagues [get] // @Router /api/v1/top-leagues [get]
func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
leagues, err := h.leagueSvc.GetFeaturedLeagues(c.Context()) leagues, err := h.leagueSvc.GetFeaturedLeagues(c.Context())
if err != nil { if err != nil {
h.logger.Error("Error while fetching top leagues", "err", err) h.mongoLoggerSvc.Error("Error while fetching top leagues",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get featured leagues", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
var topLeague []TopLeague = make([]TopLeague, 0, len(leagues)) var topLeague []TopLeague = make([]TopLeague, 0, len(leagues))
@ -161,8 +207,12 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
}, },
}) })
if err != nil { if err != nil {
fmt.Printf("Error while fetching events for top league %v \n", league.ID) h.mongoLoggerSvc.Warn("Error while fetching events for top league",
h.logger.Error("Error while fetching events for top league", "League ID", league.ID) zap.Int64("LeagueID", league.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
} }
topLeague = append(topLeague, TopLeague{ topLeague = append(topLeague, TopLeague{
LeagueID: league.ID, LeagueID: league.ID,
@ -189,17 +239,28 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
// @Success 200 {object} domain.UpcomingEvent // @Success 200 {object} domain.UpcomingEvent
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /events/{id} [get] // @Router /api/v1/events/{id} [get]
func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error { func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error {
id := c.Params("id") id := c.Params("id")
if id == "" { if id == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing id", nil, nil) h.mongoLoggerSvc.Info("Failed to parse event id",
zap.String("id", id),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Missing id")
} }
event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), id) event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), id)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve upcoming event", nil, nil) h.mongoLoggerSvc.Error("Failed to get upcoming event by id",
zap.String("eventID", id),
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, "Upcoming event retrieved successfully", event, nil) return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", event, nil)
@ -219,13 +280,19 @@ type UpdateEventStatusReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /events/{id} [delete] // @Router /api/v1/events/{id} [delete]
func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error { func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error {
eventID := c.Params("id") eventID := c.Params("id")
err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED) err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED)
if err != nil { if err != nil {
h.logger.Error("Failed to update event status", "eventID", eventID, "error", err) h.mongoLoggerSvc.Error("Failed to update event status",
zap.String("EventID", eventID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update event status")
} }
return response.WriteJSON(c, fiber.StatusOK, "Event updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Event updated successfully", nil, nil)
@ -246,13 +313,19 @@ type UpdateEventFlaggedReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /events/{id}/flag [put] // @Router /api/v1/events/{id}/flag [put]
func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error { func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
eventID := c.Params("id") eventID := c.Params("id")
var req UpdateEventFlaggedReq var req UpdateEventFlaggedReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse user id",
zap.String("eventID", eventID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
@ -262,12 +335,23 @@ func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
for field, msg := range valErrs { for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} }
h.mongoLoggerSvc.Error("Failed to update event flagged",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err := h.eventSvc.UpdateFlagged(c.Context(), eventID, req.Flagged) err := h.eventSvc.UpdateFlagged(c.Context(), eventID, req.Flagged)
if err != nil { if err != nil {
h.logger.Error("Failed to update event flagged", "eventID", eventID, "error", err) h.mongoLoggerSvc.Error("Failed to update event flagged",
zap.String("eventID", eventID),
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, "Event updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Event updated successfully", nil, nil)

View File

@ -2,9 +2,11 @@ package handlers
import ( import (
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// CreateIssue godoc // CreateIssue godoc
@ -24,12 +26,25 @@ func (h *Handler) CreateIssue(c *fiber.Ctx) error {
var req domain.ReportedIssueReq var req domain.ReportedIssueReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") h.mongoLoggerSvc.Info("Invalid request body",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
created, err := h.issueReportingSvc.CreateReportedIssue(c.Context(), req, userID, role) created, err := h.issueReportingSvc.CreateReportedIssue(c.Context(), req, userID, role)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) h.mongoLoggerSvc.Error("Failed to report issue",
zap.String("role", string(role)),
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 report issue:"+err.Error())
} }
return c.Status(fiber.StatusCreated).JSON(created) return c.Status(fiber.StatusCreated).JSON(created)
@ -50,6 +65,12 @@ func (h *Handler) CreateIssue(c *fiber.Ctx) error {
func (h *Handler) GetUserIssues(c *fiber.Ctx) error { func (h *Handler) GetUserIssues(c *fiber.Ctx) error {
userID, err := strconv.ParseInt(c.Params("user_id"), 10, 64) userID, err := strconv.ParseInt(c.Params("user_id"), 10, 64)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("Failed to parse user id",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
} }
@ -57,7 +78,13 @@ func (h *Handler) GetUserIssues(c *fiber.Ctx) error {
issues, err := h.issueReportingSvc.GetIssuesForUser(c.Context(), userID, limit, offset) issues, err := h.issueReportingSvc.GetIssuesForUser(c.Context(), userID, limit, offset)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) h.mongoLoggerSvc.Error("Failed to get user issue",
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 get user issue"+err.Error())
} }
return c.JSON(issues) return c.JSON(issues)
@ -78,7 +105,14 @@ func (h *Handler) GetAllIssues(c *fiber.Ctx) error {
issues, err := h.issueReportingSvc.GetAllIssues(c.Context(), limit, offset) issues, err := h.issueReportingSvc.GetAllIssues(c.Context(), limit, offset)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) h.mongoLoggerSvc.Error("Failed to get all issues",
zap.Int("limit", limit),
zap.Int("offset", offset),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get all issues:"+err.Error())
} }
return c.JSON(issues) return c.JSON(issues)
@ -104,11 +138,24 @@ func (h *Handler) UpdateIssueStatus(c *fiber.Ctx) error {
Status string `json:"status"` Status string `json:"status"`
} }
if err := c.BodyParser(&body); err != nil || body.Status == "" { if err := c.BodyParser(&body); err != nil || body.Status == "" {
return fiber.NewError(fiber.StatusBadRequest, "Invalid status payload") h.mongoLoggerSvc.Info("Invalid status payload",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid status payload"+err.Error())
} }
if err := h.issueReportingSvc.UpdateIssueStatus(c.Context(), issueID, body.Status); err != nil { if err := h.issueReportingSvc.UpdateIssueStatus(c.Context(), issueID, body.Status); err != nil {
h.mongoLoggerSvc.Error("Failed to update issue status",
zap.Int64("issueID", issueID),
zap.String("status", body.Status),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
return c.SendStatus(fiber.StatusNoContent) return c.SendStatus(fiber.StatusNoContent)
@ -130,6 +177,12 @@ func (h *Handler) DeleteIssue(c *fiber.Ctx) error {
} }
if err := h.issueReportingSvc.DeleteIssue(c.Context(), issueID); err != nil { if err := h.issueReportingSvc.DeleteIssue(c.Context(), issueID); err != nil {
h.mongoLoggerSvc.Error("Failed to delete issue",
zap.Int64("issueID", issueID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }

View File

@ -3,10 +3,12 @@ package handlers
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// GetAllLeagues godoc // GetAllLeagues godoc
@ -18,7 +20,7 @@ import (
// @Success 200 {array} domain.League // @Success 200 {array} domain.League
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /leagues [get] // @Router /api/v1/leagues [get]
func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
page := c.QueryInt("page", 1) page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10) pageSize := c.QueryInt("page_size", 10)
@ -49,8 +51,13 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
if sportIDQuery != "" { if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery) sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil { if err != nil {
h.logger.Error("invalid sport id", "error", err) h.mongoLoggerSvc.Info("invalid sport id",
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) zap.String("sport_id", sportIDQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
} }
sportID = domain.ValidInt32{ sportID = domain.ValidInt32{
Value: int32(sportIDint), Value: int32(sportIDint),
@ -68,8 +75,12 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
if err != nil { if err != nil {
fmt.Printf("Error fetching league %v \n", err) fmt.Printf("Error fetching league %v \n", err)
h.logger.Error("Failed to get leagues", "error", err) h.mongoLoggerSvc.Error("Failed to get all leagues",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get leagues", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get leagues:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "All leagues retrieved", leagues, nil) return response.WriteJSON(c, fiber.StatusOK, "All leagues retrieved", leagues, nil)
} }
@ -78,59 +89,132 @@ type SetLeagueActiveReq struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }
// SetLeagueActive godoc
// @Summary Set the league to active
// @Description Set the league to active
// @Tags leagues
// @Accept json
// @Produce json
// @Param id path int true "League ID"
// @Param active body SetLeagueActiveReq true "League Active Request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/leagues/{id}/set-active [put]
func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
fmt.Printf("Set Active Leagues")
leagueIdStr := c.Params("id") leagueIdStr := c.Params("id")
if leagueIdStr == "" { if leagueIdStr == "" {
response.WriteJSON(c, fiber.StatusBadRequest, "Missing league id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Missing league id")
} }
leagueId, err := strconv.Atoi(leagueIdStr) leagueId, err := strconv.Atoi(leagueIdStr)
if err != nil { if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
} }
var req SetLeagueActiveReq var req SetLeagueActiveReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("SetLeagueReq failed", "error", err) h.logger.Error("SetLeagueReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to parse request", err, nil) h.mongoLoggerSvc.Error("SetLeagueReq failed to parse request body",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse request:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate SetLeagueActiveReq",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil { if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil {
response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil) h.mongoLoggerSvc.Error("Failed to update league active",
zap.Int64("userID", int64(leagueId)),
zap.Bool("is_active", req.IsActive),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil)
} }
func (h *Handler) SetLeagueAsFeatured(c *fiber.Ctx) error { type SetLeagueAsFeatured struct {
fmt.Printf("Set Active Leagues") IsFeatured bool `json:"is_featured" example:"true"`
}
// SetLeagueFeatured godoc
// @Summary Set the league to featured/un-featured
// @Description Set the league to featured/un-featured
// @Tags leagues
// @Accept json
// @Produce json
// @Param id path int true "League ID"
// @Param active body SetLeagueAsFeatured true "League Featured Request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/leagues/{id}/featured [put]
func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error {
leagueIdStr := c.Params("id") leagueIdStr := c.Params("id")
if leagueIdStr == "" { if leagueIdStr == "" {
response.WriteJSON(c, fiber.StatusBadRequest, "Missing league id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "Missing league id")
} }
leagueId, err := strconv.Atoi(leagueIdStr) leagueId, err := strconv.Atoi(leagueIdStr)
if err != nil { if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
} }
var req SetLeagueActiveReq var req SetLeagueAsFeatured
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("SetLeagueReq failed", "error", err) h.logger.Error("SetLeagueFeaturedReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to parse request", err, nil) h.mongoLoggerSvc.Info("SetLeagueFeaturedReq failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
} for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate SetLeagueFeaturedReq",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil { }
response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil) err = h.leagueSvc.UpdateLeague(c.Context(), domain.UpdateLeague{
ID: int64(leagueId),
})
if err != nil {
h.mongoLoggerSvc.Error("Failed to update league",
zap.Int64("leagueID", int64(leagueId)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil)

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
@ -8,6 +9,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CreateManagerReq struct { type CreateManagerReq struct {
@ -30,7 +32,7 @@ type CreateManagerReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /managers [post] // @Router /api/v1/managers [post]
func (h *Handler) CreateManager(c *fiber.Ctx) error { func (h *Handler) CreateManager(c *fiber.Ctx) error {
// Get user_id from middleware // Get user_id from middleware
@ -38,11 +40,26 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
var req CreateManagerReq var req CreateManagerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err) h.logger.Error("RegisterUser failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) h.mongoLoggerSvc.Info("CreateManager failed to create manager",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate CreateManager",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
var companyID domain.ValidInt64 var companyID domain.ValidInt64
@ -50,7 +67,11 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
if role == domain.RoleSuperAdmin { if role == domain.RoleSuperAdmin {
if req.CompanyID == nil { if req.CompanyID == nil {
h.logger.Error("RegisterUser failed error: company id is required") h.logger.Error("RegisterUser failed error: company id is required")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", "Company ID is required", nil) h.mongoLoggerSvc.Info("RegisterUser failed error: company id is required",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Company ID is required for super-admin")
} }
companyID = domain.ValidInt64{ companyID = domain.ValidInt64{
Value: *req.CompanyID, Value: *req.CompanyID,
@ -71,8 +92,12 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
} }
_, err := h.userSvc.CreateUser(c.Context(), user, true) _, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil { if err != nil {
h.logger.Error("CreateManager failed", "error", err) h.mongoLoggerSvc.Error("CreateManager failed to create manager",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create manager", nil, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create manager:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil)
@ -106,14 +131,19 @@ type ManagersRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /managers [get] // @Router /api/v1/managers [get]
func (h *Handler) GetAllManagers(c *fiber.Ctx) error { func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) companyId := c.Locals("company_id").(domain.ValidInt64)
// Checking to make sure that admin user has a company id in the token // Checking to make sure that admin user has a company id in the token
if role != domain.RoleSuperAdmin && !companyId.Valid { if role != domain.RoleSuperAdmin && !companyId.Valid {
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") h.mongoLoggerSvc.Error("Cannot get company ID from context",
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID from context")
} }
searchQuery := c.Query("query") searchQuery := c.Query("query")
@ -127,8 +157,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_before format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("created_before", 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -141,8 +176,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid created_after format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -167,12 +207,28 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
} }
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate get all filters",
zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil { if err != nil {
h.logger.Error("GetAllManagers failed", "error", err) h.logger.Error("GetAllManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", err, nil) h.mongoLoggerSvc.Error("GetAllManagers failed to get all managers",
zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Managers"+err.Error())
} }
var result []ManagersRes = make([]ManagersRes, len(managers)) var result []ManagersRes = make([]ManagersRes, len(managers))
@ -182,8 +238,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if err == authentication.ErrRefreshTokenNotFound { if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &manager.CreatedAt lastLogin = &manager.CreatedAt
} else { } else {
h.logger.Error("Failed to get user last login", "userID", manager.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Int64("userID", manager.ID),
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())
} }
} }
result[index] = ManagersRes{ result[index] = ManagersRes{
@ -218,7 +279,7 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /managers/{id} [get] // @Router /api/v1/managers/{id} [get]
func (h *Handler) GetManagerByID(c *fiber.Ctx) error { func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) companyId := c.Locals("company_id").(domain.ValidInt64)
@ -226,11 +287,22 @@ func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
// Only Super Admin / Admin / Branch Manager can view this route // Only Super Admin / Admin / Branch Manager can view this route
if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager { if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager {
return fiber.NewError(fiber.StatusUnauthorized, "Role Unauthorized") h.mongoLoggerSvc.Warn("Attempt to access from unauthorized role",
zap.Int64("userID", requestUserID),
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "This role cannot view this route")
} }
if role != domain.RoleSuperAdmin && !companyId.Valid { if role != domain.RoleSuperAdmin && !companyId.Valid {
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID") h.mongoLoggerSvc.Error("Cannot get company ID in context",
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context")
} }
userIDstr := c.Params("id") userIDstr := c.Params("id")
@ -241,24 +313,47 @@ func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get managers") h.mongoLoggerSvc.Error("Failed to get manager by id",
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 get managers:"+err.Error())
} }
// A Branch Manager can only fetch his own branch info // A Branch Manager can only fetch his own branch info
if role == domain.RoleBranchManager && user.ID != requestUserID { if role == domain.RoleBranchManager && user.ID != requestUserID {
return fiber.NewError(fiber.StatusBadRequest, "User Access Not Allowed") h.mongoLoggerSvc.Warn("Attempt to access another branch manager info",
zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "User Access Not Allowed")
} }
// Check that only admin from company can view this route // Check that only admin from company can view this route
if role != domain.RoleSuperAdmin && user.CompanyID.Value != companyId.Value { if role != domain.RoleSuperAdmin && user.CompanyID.Value != companyId.Value {
return fiber.NewError(fiber.StatusBadRequest, "Only company user can view manager info") h.mongoLoggerSvc.Warn("Attempt to access info from another company",
zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Cannot access another company information")
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve 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 lastLogin = &user.CreatedAt
@ -301,13 +396,14 @@ type updateManagerReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /managers/{id} [put] // @Router /api/v1/managers/{id} [put]
func (h *Handler) UpdateManagers(c *fiber.Ctx) error { func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
var req updateManagerReq var req updateManagerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateManagers failed", "error", err) h.logger.Error("UpdateManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
} }

View File

@ -7,6 +7,8 @@ import (
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
@ -14,6 +16,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/valyala/fasthttp/fasthttpadaptor" "github.com/valyala/fasthttp/fasthttpadaptor"
"go.uber.org/zap"
) )
func hijackHTTP(c *fiber.Ctx) (net.Conn, http.ResponseWriter, error) { func hijackHTTP(c *fiber.Ctx) (net.Conn, http.ResponseWriter, error) {
@ -45,30 +48,49 @@ func hijackHTTP(c *fiber.Ctx) (net.Conn, http.ResponseWriter, error) {
func (h *Handler) ConnectSocket(c *fiber.Ctx) error { func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(int64) userID, ok := c.Locals("userID").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Info("Invalid user ID in context",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusUnauthorized),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
} }
// Convert *fiber.Ctx to *http.Request // Convert *fiber.Ctx to *http.Request
req, err := adaptor.ConvertRequest(c, false) req, err := adaptor.ConvertRequest(c, false)
if err != nil { if err != nil {
h.logger.Error("Failed to convert request", "error", err) h.mongoLoggerSvc.Error("Failed to convert socket request",
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 convert request") return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert request")
} }
// Create a net.Conn hijacked from the fasthttp context // Create a net.Conn hijacked from the fasthttp context
netConn, rw, err := hijackHTTP(c) netConn, rw, err := hijackHTTP(c)
if err != nil { if err != nil {
h.logger.Error("Failed to hijack connection", "error", err) h.mongoLoggerSvc.Error("Failed to hijack connection",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to hijack connection") 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 hijack connection:"+err.Error())
} }
// Upgrade the connection using Gorilla's Upgrader // Upgrade the connection using Gorilla's Upgrader
conn, err := ws.Upgrader.Upgrade(rw, req, nil) conn, err := ws.Upgrader.Upgrade(rw, req, nil)
if err != nil { if err != nil {
h.logger.Error("WebSocket upgrade failed", "error", err) h.mongoLoggerSvc.Error("WebSocket upgrade failed",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
netConn.Close() netConn.Close()
return fiber.NewError(fiber.StatusInternalServerError, "WebSocket upgrade failed") return fiber.NewError(fiber.StatusInternalServerError, "WebSocket upgrade failed:"+err.Error())
} }
client := &ws.Client{ client := &ws.Client{
@ -88,9 +110,19 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
_, _, err := conn.ReadMessage() _, _, err := conn.ReadMessage()
if err != nil { if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) { if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
h.logger.Info("WebSocket closed normally", "userID", userID) h.mongoLoggerSvc.Info("WebSocket closed normally",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
} else { } else {
h.logger.Warn("Unexpected WebSocket closure", "userID", userID, "error", err) h.mongoLoggerSvc.Warn("Unexpected WebSocket closure",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
} }
break break
} }
@ -106,20 +138,34 @@ func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
var req Request var req Request
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse request body", "error", err) h.mongoLoggerSvc.Info("Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Error("Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "invalid user ID in context")
} }
fmt.Printf("Notification IDs: %v \n", req.NotificationIDs) fmt.Printf("Notification IDs: %v \n", req.NotificationIDs)
if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil { if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil {
h.logger.Error("Failed to mark notifications as read", "notificationID", req.NotificationIDs, "error", err) h.mongoLoggerSvc.Error("Failed to mark notifications as read",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status") zap.String("notificationID", strings.Join(req.NotificationIDs, ",")),
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 update notification status:", err.Error())
} }
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"}) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"})
@ -141,7 +187,11 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
var req Request var req Request
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to parse request body", "error", err) h.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
@ -174,11 +224,20 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
} }
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil { if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send single notification", "recipientID", req.RecipientID, "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to send single notification",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send notification") zap.Int64("recipientID", req.RecipientID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send notification:"+err.Error())
} }
h.logger.Info("[NotificationSvc.CreateAndSendNotification] Single notification sent successfully", "recipientID", req.RecipientID, "type", req.Type) h.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Single notification sent successfully",
zap.Int64("recipientID", req.RecipientID),
zap.String("type", string(req.Type)),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID}) return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID})
case domain.NotificationDeliverySchemeBulk: case domain.NotificationDeliverySchemeBulk:
@ -186,12 +245,16 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
Role: string(req.Reciever), Role: string(req.Reciever),
}) })
if err != nil { if err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients") zap.Int64("RecipientID", req.RecipientID),
zap.String("Reciever", string(req.Reciever)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients:"+err.Error())
} }
fmt.Printf("Number of Recipients %d \n", len(recipients))
notificationIDs := make([]string, 0, len(recipients)) notificationIDs := make([]string, 0, len(recipients))
for _, user := range recipients { for _, user := range recipients {
notification := &domain.Notification{ notification := &domain.Notification{
@ -210,13 +273,23 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
} }
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil { if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "UserID", user.ID, "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification",
zap.Int64("UserID", user.ID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
continue continue
} }
notificationIDs = append(notificationIDs, notification.ID) notificationIDs = append(notificationIDs, notification.ID)
} }
h.logger.Info("[NotificationSvc.CreateAndSendNotification] Bulk notification sent successfully", "recipient_count", len(recipients), "type", req.Type) h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Bulk notification sent successfully",
zap.Int("recipient_count", len(recipients)),
zap.String("type", string(req.Type)),
zap.Int("status_code", fiber.StatusCreated),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{ return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Bulk notification sent successfully", "message": "Bulk notification sent successfully",
"recipient_count": len(recipients), "recipient_count": len(recipients),
@ -224,7 +297,11 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
}) })
default: default:
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Invalid delivery scheme", "delivery_scheme", req.DeliveryScheme) h.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Invalid delivery scheme",
zap.String("delivery_scheme", string(req.DeliveryScheme)),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid delivery scheme") return fiber.NewError(fiber.StatusBadRequest, "Invalid delivery scheme")
} }
} }
@ -236,25 +313,45 @@ func (h *Handler) GetNotifications(c *fiber.Ctx) error {
// Convert limit and offset to integers // Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr) limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 { if err != nil || limit <= 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid limit value", "error", err) h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid limit value",
zap.String("limit", limitStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
} }
offset, err := strconv.Atoi(offsetStr) offset, err := strconv.Atoi(offsetStr)
if err != nil || offset < 0 { if err != nil || offset < 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid offset value", "error", err) h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid offset value",
zap.String("offset", offsetStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid offset value") return fiber.NewError(fiber.StatusBadRequest, "Invalid offset value")
} }
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context") h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
notifications, err := h.notificationSvc.ListNotifications(context.Background(), userID, limit, offset) notifications, err := h.notificationSvc.ListNotifications(context.Background(), userID, limit, offset)
if err != nil { if err != nil {
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications") 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 fetch notifications:"+err.Error())
} }
return c.Status(fiber.StatusOK).JSON(fiber.Map{ return c.Status(fiber.StatusOK).JSON(fiber.Map{
@ -274,15 +371,24 @@ func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context") h.mongoLoggerSvc.Error("NotificationSvc.GetNotifications] Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID) total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count", "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications") 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 fetch notifications:"+err.Error())
} }
return c.Status(fiber.StatusOK).JSON(fiber.Map{ return c.Status(fiber.StatusOK).JSON(fiber.Map{
@ -297,18 +403,33 @@ func (h *Handler) GetAllNotifications(c *fiber.Ctx) error {
// Convert limit and offset to integers // Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr) limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 { if err != nil || limit <= 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid limit value", "error", err) h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid limit value",
zap.String("limit", limitStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
} }
page, err := strconv.Atoi(pageStr) page, err := strconv.Atoi(pageStr)
if err != nil || page <= 0 { if err != nil || page <= 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid page value", "error", err) h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid page value",
zap.String("page", pageStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid page value") return fiber.NewError(fiber.StatusBadRequest, "Invalid page value")
} }
notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit)) notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit))
if err != nil { if err != nil {
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err) h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications",
zap.Int64("limit", int64(limit)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications") return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
} }

View File

@ -2,9 +2,11 @@ package handlers
import ( import (
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// GetALLPrematchOdds // GetALLPrematchOdds
@ -15,12 +17,17 @@ import (
// @Produce json // @Produce json
// @Success 200 {array} domain.Odd // @Success 200 {array} domain.Odd
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /odds [get] // @Router /api/v1/odds [get]
func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error { func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context()) odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context())
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all prematch odds", nil, nil) h.mongoLoggerSvc.Error("Failed to retrieve all prematch odds",
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, "All prematch odds retrieved successfully", odds, nil) return response.WriteJSON(c, fiber.StatusOK, "All prematch odds retrieved successfully", odds, nil)
@ -38,24 +45,39 @@ func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
// @Success 200 {array} domain.RawOddsByMarketID // @Success 200 {array} domain.RawOddsByMarketID
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /odds/upcoming/{upcoming_id}/market/{market_id} [get] // @Router /api/v1/odds/upcoming/{upcoming_id}/market/{market_id} [get]
func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error { func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
marketID := c.Params("market_id") marketID := c.Params("market_id")
upcomingID := c.Params("upcoming_id")
if marketID == "" { if marketID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing market_id", nil, nil) h.mongoLoggerSvc.Info("Missing market_id",
zap.String("market_id", marketID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Missing market_id")
} }
upcomingID := c.Params("upcoming_id")
if upcomingID == "" { if upcomingID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil) h.mongoLoggerSvc.Info("Missing upcoming_id",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id")
} }
rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID) rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID)
if err != nil { if err != nil {
// fmt.Printf("Failed to fetch raw odds: %v market_id:%v upcomingID:%v\n", err, marketID, upcomingID) h.mongoLoggerSvc.Error("Failed to get raw odds by market ID",
h.logger.Error("Failed to get raw odds by market ID", "marketID", marketID, "upcomingID", upcomingID, "error", err) zap.String("marketID", marketID),
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil) zap.String("upcomingID", upcomingID),
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, "Raw odds retrieved successfully", rawOdds, nil) return response.WriteJSON(c, fiber.StatusOK, "Raw odds retrieved successfully", rawOdds, nil)
@ -73,30 +95,52 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
// @Success 200 {array} domain.Odd // @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /odds/upcoming/{upcoming_id} [get] // @Router /api/v1/odds/upcoming/{upcoming_id} [get]
func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error { func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error {
upcomingID := c.Params("upcoming_id") upcomingID := c.Params("upcoming_id")
if upcomingID == "" { if upcomingID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil) h.mongoLoggerSvc.Info("Missing upcoming_id",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id")
} }
limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10
if err != nil || limit <= 0 { if err != nil || limit <= 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid limit value", nil, nil) h.mongoLoggerSvc.Info("Invalid limit value",
zap.Int("limit", limit),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
} }
offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0
if err != nil || offset < 0 { if err != nil || offset < 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil) h.mongoLoggerSvc.Info("Invalid offset value",
zap.Int("offset", offset),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID) odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil) h.mongoLoggerSvc.Error("Failed to retrieve prematch odds",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve prematch odds"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil) return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
} }

View File

@ -1,20 +1,33 @@
package handlers package handlers
import ( import (
"fmt"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
func (h *Handler) CreateReferralCode(c *fiber.Ctx) error { func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Info("Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("userID", userID),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
if err := h.referralSvc.CreateReferral(c.Context(), userID); err != nil { if err := h.referralSvc.CreateReferral(c.Context(), userID); err != nil {
h.logger.Error("Failed to create referral", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to create referral",
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 create referral") return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral")
} }
@ -24,29 +37,54 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error { func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
var req domain.ReferralSettingsReq var req domain.ReferralSettingsReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse settings", "error", err) h.mongoLoggerSvc.Info("Failed to parse settings",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate settings",
zap.String("errMsg", errMsg),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
settings, err := h.referralSvc.GetReferralSettings(c.Context()) settings, err := h.referralSvc.GetReferralSettings(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to fetch previous referral setting", "error", err) h.mongoLoggerSvc.Error("Failed to fetch previous referral setting",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral")
} }
// only allow one referral setting for now // only allow one referral setting for now
// for future it can be multiple and be able to choose from them // for future it can be multiple and be able to choose from them
if settings != nil { if settings != nil {
h.logger.Error("referral setting already exists", "error", err) h.mongoLoggerSvc.Error("referral setting already exists",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "referral setting already exists") return fiber.NewError(fiber.StatusInternalServerError, "referral setting already exists")
} }
if err := h.referralSvc.CreateReferralSettings(c.Context(), req); err != nil { if err := h.referralSvc.CreateReferralSettings(c.Context(), req); err != nil {
h.logger.Error("Failed to create referral setting", "error", err) h.mongoLoggerSvc.Error("Failed to create referral setting",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral")
} }
@ -63,23 +101,37 @@ func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security Bearer // @Security Bearer
// @Router /referral/stats [get] // @Router /api/v1/referral/stats [get]
func (h *Handler) GetReferralStats(c *fiber.Ctx) error { func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Error("Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id")
} }
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get user",
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") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
} }
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.PhoneNumber) stats, err := h.referralSvc.GetReferralStats(c.Context(), user.PhoneNumber)
if err != nil { if err != nil {
h.logger.Error("Failed to get referral stats", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get referral stats",
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 referral stats") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral stats")
} }
@ -98,34 +150,60 @@ func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
// @Failure 403 {object} response.APIResponse // @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security Bearer // @Security Bearer
// @Router /referral/settings [put] // @Router /api/v1/referral/settings [put]
func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error { func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") h.mongoLoggerSvc.Error("Failed to delete user",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id")
} }
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get user",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
if user.Role != domain.RoleAdmin { if user.Role != domain.RoleAdmin {
h.mongoLoggerSvc.Error("Access Forbidden",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Admin access required") return fiber.NewError(fiber.StatusForbidden, "Admin access required")
} }
var settings domain.ReferralSettings var settings domain.ReferralSettings
if err := c.BodyParser(&settings); err != nil { if err := c.BodyParser(&settings); err != nil {
h.logger.Error("Failed to parse settings", "error", err) h.mongoLoggerSvc.Info("Failed to parse settings",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
settings.UpdatedBy = user.PhoneNumber settings.UpdatedBy = user.PhoneNumber
if err := h.referralSvc.UpdateReferralSettings(c.Context(), &settings); err != nil { if err := h.referralSvc.UpdateReferralSettings(c.Context(), &settings); err != nil {
h.logger.Error("Failed to update referral settings", "error", err) h.mongoLoggerSvc.Error("Failed to update referral settings",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update referral settings") zap.Int64("userID", userID),
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, "Referral settings updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Referral settings updated successfully", nil, nil)
@ -142,7 +220,7 @@ func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error {
// @Failure 403 {object} response.APIResponse // @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security Bearer // @Security Bearer
// @Router /referral/settings [get] // @Router /api/v1/referral/settings [get]
func (h *Handler) GetReferralSettings(c *fiber.Ctx) error { func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
// userID, ok := c.Locals("user_id").(int64) // userID, ok := c.Locals("user_id").(int64)
// if !ok || userID == 0 { // if !ok || userID == 0 {
@ -153,18 +231,34 @@ func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get user",
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") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
} }
if user.Role != domain.RoleAdmin { if user.Role != domain.RoleAdmin {
h.mongoLoggerSvc.Error("Admin access required",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Admin access required") return fiber.NewError(fiber.StatusForbidden, "Admin access required")
} }
settings, err := h.referralSvc.GetReferralSettings(c.Context()) settings, err := h.referralSvc.GetReferralSettings(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to get referral settings", "error", err) h.mongoLoggerSvc.Error("Failed to get referral settings",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral settings") zap.Int64("userID", userID),
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, "Referral settings retrieved successfully", settings, nil) return response.WriteJSON(c, fiber.StatusOK, "Referral settings retrieved successfully", settings, nil)

View File

@ -2,10 +2,12 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type ResultRes struct { type ResultRes struct {
@ -24,17 +26,26 @@ type ResultRes struct {
// @Success 200 {array} ResultRes // @Success 200 {array} ResultRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /result/{id} [get] // @Router /api/v1/result/{id} [get]
func (h *Handler) GetResultsByEventID(c *fiber.Ctx) error { func (h *Handler) GetResultsByEventID(c *fiber.Ctx) error {
eventID := c.Params("id") eventID := c.Params("id")
if eventID == "" { if eventID == "" {
h.logger.Error("Event ID is required") h.mongoLoggerSvc.Info("Event ID is required",
zap.String("eventID", eventID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Event ID is required") return fiber.NewError(fiber.StatusBadRequest, "Event ID is required")
} }
results, outcomes, err := h.resultSvc.GetResultsForEvent(c.Context(), eventID) results, outcomes, err := h.resultSvc.GetResultsForEvent(c.Context(), eventID)
if err != nil { if err != nil {
h.logger.Error("Failed to get results by Event ID", "eventID", eventID, "error", err) h.mongoLoggerSvc.Error("Failed to get results by Event ID",
zap.String("eventID", eventID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve results") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve results")
} }

View File

@ -2,13 +2,13 @@ package handlers
import ( import (
"fmt" "fmt"
"log/slog"
"strconv" "strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// CreateShopBet godoc // CreateShopBet godoc
@ -18,10 +18,10 @@ import (
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param createBet body domain.ShopBetReq true "create bet" // @Param createBet body domain.ShopBetReq true "create bet"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/bet [post] // @Router /api/v1/shop/bet [post]
func (h *Handler) CreateShopBet(c *fiber.Ctx) error { func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
@ -30,20 +30,42 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
var req domain.ShopBetReq var req domain.ShopBetReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateBetReq failed to parse request", "error", err) h.mongoLoggerSvc.Info("CreateBetReq failed to parse request",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid request body"+err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
h.logger.Error("CreateBetReq failed v", "error", valErrs) var errMsg string
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
shopBet, err := h.transactionSvc.CreateShopBet(c.Context(), userID, role, company_id, req) shopBet, err := h.transactionSvc.CreateShopBet(c.Context(), userID, role, company_id, req)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to cashout bet", err, nil) var statusCode int
if isBetError := h.betSvc.CheckIfBetError(err); isBetError {
statusCode = fiber.StatusBadRequest
} else {
statusCode = fiber.StatusInternalServerError
}
h.mongoLoggerSvc.Info("Failed to create shop bet",
zap.Int64("userID", userID),
zap.String("role", string(role)),
zap.Int("status_code", statusCode),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(statusCode, "failed to create shop bet"+err.Error())
} }
res := domain.ConvertShopBet(shopBet) res := domain.ConvertShopBet(shopBet)
@ -57,10 +79,10 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param createBet body domain.CashoutReq true "cashout bet" // @Param createBet body domain.CashoutReq true "cashout bet"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/bet/{id} [get] // @Router /api/v1/shop/bet/{id} [get]
func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error { func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
betIDstr := c.Params("id") betIDstr := c.Params("id")
@ -68,15 +90,25 @@ func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
betID, err := strconv.ParseInt(betIDstr, 10, 64) betID, err := strconv.ParseInt(betIDstr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("CashoutReq failed bet id is invalid", "error", nil) h.mongoLoggerSvc.Info("GetShopBetByBetID failed bet id is invalid",
return response.WriteJSON(c, fiber.StatusBadRequest, "bet ID is invalid", nil, nil) zap.String("betID", betIDstr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "bet ID is invalid:"+err.Error())
} }
bet, err := h.transactionSvc.GetShopBetByBetID(c.Context(), betID) bet, err := h.transactionSvc.GetShopBetByBetID(c.Context(), betID)
if err != nil { if err != nil {
h.logger.Error("CashoutReq failed invalid bet id", "error", err) h.mongoLoggerSvc.Error("GetShopBetByBetID failed invalid bet id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil) zap.Int64("betID", betID),
zap.Int("status_code", fiber.StatusNotFound),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusNotFound, err.Error())
} }
res := domain.ConvertShopBetDetail(bet) res := domain.ConvertShopBetDetail(bet)
@ -91,10 +123,10 @@ func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param cashoutBet body domain.CashoutReq true "cashout bet" // @Param cashoutBet body domain.CashoutReq true "cashout bet"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/bet/{id}/cashout [post] // @Router /api/v1/shop/bet/{id}/cashout [post]
func (h *Handler) CashoutBet(c *fiber.Ctx) error { func (h *Handler) CashoutBet(c *fiber.Ctx) error {
betIDStr := c.Params("id") betIDStr := c.Params("id")
@ -102,8 +134,13 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
betID, err := strconv.ParseInt(betIDStr, 10, 64) betID, err := strconv.ParseInt(betIDStr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("CashoutReq invalid bet id", "error", err) h.mongoLoggerSvc.Info("CashoutReq invalid bet id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet id", err, nil) zap.String("betID", betIDStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet id")
} }
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
@ -112,20 +149,41 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
var req domain.CashoutReq var req domain.CashoutReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CashoutReq failed to parse request", "error", err) h.mongoLoggerSvc.Info("CashoutReq failed to parse request",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
h.logger.Error("CashoutReq failed v", "error", valErrs) var errMsg string
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("CashoutReq validation failed",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
transaction, err := h.transactionSvc.CashoutBet(c.Context(), betID, userID, role, req, companyID) transaction, err := h.transactionSvc.CashoutBet(c.Context(), betID, userID, role, req, companyID)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to cashout bet", err, nil) h.mongoLoggerSvc.Error("Failed to cashout bet",
zap.Int64("userID", userID),
zap.Int64("betID", betID),
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := domain.ConvertShopTransaction(transaction) res := domain.ConvertShopTransaction(transaction)
@ -139,10 +197,10 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param cashoutBet body domain.CashoutReq true "cashout bet" // @Param cashoutBet body domain.CashoutReq true "cashout bet"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/cashout [post] // @Router /api/v1/shop/cashout [post]
func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error { func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
@ -151,27 +209,52 @@ func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error {
var req domain.CashoutReq var req domain.CashoutReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CashoutReq failed to parse request", "error", err) h.mongoLoggerSvc.Info("CashoutReq failed to parse request",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
h.logger.Error("CashoutReq failed v", "error", valErrs) var errMsg string
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("CashoutReq validation failed",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("error", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), req.CashoutID) bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), req.CashoutID)
if err != nil { if err != nil {
h.logger.Error("CashoutReq failed invalid cashout id", "error", err) h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil) zap.String("CashoutID", req.CashoutID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
transaction, err := h.transactionSvc.CashoutBet(c.Context(), bet.BetID, userID, role, req, companyID) transaction, err := h.transactionSvc.CashoutBet(c.Context(), bet.BetID, userID, role, req, companyID)
if err != nil { if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to cashout bet", err, nil) h.mongoLoggerSvc.Info("Failed to cashout bet",
zap.Int64("BetID", bet.BetID),
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
res := domain.ConvertShopTransaction(transaction) res := domain.ConvertShopTransaction(transaction)
@ -185,24 +268,33 @@ func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param createBet body domain.CashoutReq true "cashout bet" // @Param createBet body domain.CashoutReq true "cashout bet"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/cashout/{id} [get] // @Router /api/v1/shop/cashout/{id} [get]
func (h *Handler) GetShopBetByCashoutID(c *fiber.Ctx) error { func (h *Handler) GetShopBetByCashoutID(c *fiber.Ctx) error {
cashoutID := c.Params("id") cashoutID := c.Params("id")
if cashoutID == "" { if cashoutID == "" {
h.logger.Error("CashoutReq failed cashout id is required", "error", nil) h.mongoLoggerSvc.Info("CashoutReq failed cashout id is required",
return response.WriteJSON(c, fiber.StatusBadRequest, "cashout ID is required", nil, nil) zap.String("cashoutID", cashoutID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "cashout ID is required")
} }
bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), cashoutID) bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), cashoutID)
if err != nil { if err != nil {
h.logger.Error("CashoutReq failed invalid cashout id", "error", err) h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil) zap.String("CashoutID", cashoutID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
res := domain.ConvertShopBetDetail(bet) res := domain.ConvertShopBetDetail(bet)
@ -217,10 +309,10 @@ func (h *Handler) GetShopBetByCashoutID(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param transferToWallet body domain.ShopDepositReq true "ShopDepositReq" // @Param transferToWallet body domain.ShopDepositReq true "ShopDepositReq"
// @Success 200 {object} TransferWalletRes // @Success 200 {object} domain.ShopDepositRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/deposit [post] // @Router /api/v1/shop/deposit [post]
func (h *Handler) DepositForCustomer(c *fiber.Ctx) error { func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
// Get sender ID from the cashier // Get sender ID from the cashier
@ -230,7 +322,12 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
var req domain.ShopDepositReq var req domain.ShopDepositReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateTransferReq failed", "error", err) h.mongoLoggerSvc.Info("CreateTransferReq failed",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
@ -240,14 +337,26 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
for field, msg := range valErrs { for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} }
h.mongoLoggerSvc.Info("Failed to validate customer deposit",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("error", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req) deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req)
if err != nil { if err != nil {
fmt.Printf("Shop Deposit Error %v \n", err) h.mongoLoggerSvc.Info("failed to create shop deposit",
return fiber.NewError(fiber.StatusBadRequest, err.Error()) zap.Int64("userID", userID),
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := domain.ConvertShopDeposit(deposit) res := domain.ConvertShopDeposit(deposit)
@ -262,10 +371,10 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
// @Tags transaction // @Tags transaction
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {array} TransactionRes // @Success 200 {array} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/transaction [get] // @Router /api/v1/shop/transaction [get]
func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
// Get user_id from middleware // Get user_id from middleware
// userID := c.Locals("user_id").(int64) // userID := c.Locals("user_id").(int64)
@ -284,8 +393,13 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
if createdBeforeQuery != "" { if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid start_time format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdBefore", 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{ createdBefore = domain.ValidTime{
Value: createdBeforeParsed, Value: createdBeforeParsed,
@ -298,8 +412,13 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
if createdAfterQuery != "" { if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { if err != nil {
h.logger.Error("invalid start_time format", "error", err) h.mongoLoggerSvc.Info("invalid start_time format",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil) zap.String("createdAfter", 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{ createdAfter = domain.ValidTime{
Value: createdAfterParsed, Value: createdAfterParsed,
@ -317,8 +436,12 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
}) })
if err != nil { if err != nil {
h.logger.Error("Failed to get transactions", "error", err) h.mongoLoggerSvc.Info("Failed to get transactions",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := make([]domain.ShopTransactionRes, len(transactions)) res := make([]domain.ShopTransactionRes, len(transactions))
@ -337,21 +460,31 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Transaction ID" // @Param id path int true "Transaction ID"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/transaction/{id} [get] // @Router /api/v1/shop/transaction/{id} [get]
func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
transactionID := c.Params("id") transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64) id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) h.mongoLoggerSvc.Info("Invalid transaction ID",
zap.String("transactionID", transactionID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
} }
transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) h.mongoLoggerSvc.Error("Failed to get shop transaction by ID",
zap.Int64("transactionID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction")
} }
@ -366,31 +499,37 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Transaction ID" // @Param id path int true "Transaction ID"
// @Success 200 {object} TransactionRes // @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/transaction/{id}/bet [get] // @Router /api/v1/shop/transaction/{id}/bet [get]
func (h *Handler) GetShopBetByTransactionID(c *fiber.Ctx) error { func (h *Handler) GetShopBetByTransactionID(c *fiber.Ctx) error {
transactionID := c.Params("id") transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64) id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) h.mongoLoggerSvc.Info("Invalid transaction ID",
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") zap.String("transactionID", transactionID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
transaction, err := h.transactionSvc.GetShopBetByShopTransactionID(c.Context(), id) transaction, err := h.transactionSvc.GetShopBetByShopTransactionID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) h.mongoLoggerSvc.Error("Failed to get transaction by ID",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") zap.Int64("transactionID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := domain.ConvertShopBetDetail(transaction) res := domain.ConvertShopBetDetail(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Shop bet retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Shop bet retrieved successfully", res, nil)
} }
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" example:"true"`
}
// UpdateTransactionVerified godoc // UpdateTransactionVerified godoc
// @Summary Updates the verified field of a transaction // @Summary Updates the verified field of a transaction
// @Description Updates the verified status of a transaction // @Description Updates the verified status of a transaction
@ -398,11 +537,11 @@ type UpdateTransactionVerifiedReq struct {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Transaction ID" // @Param id path int true "Transaction ID"
// @Param updateVerified body UpdateTransactionVerifiedReq true "Updates Transaction Verification" // @Param updateVerified body domain.UpdateTransactionVerifiedReq true "Updates Transaction Verification"
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /shop/transaction/{id} [put] // @Router /api/v1/shop/transaction/{id} [put]
func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
transactionID := c.Params("id") transactionID := c.Params("id")
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
@ -412,25 +551,48 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
id, err := strconv.ParseInt(transactionID, 10, 64) id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) h.mongoLoggerSvc.Info("Invalid transaction ID",
zap.String("transactionID", transactionID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
} }
var req UpdateTransactionVerifiedReq var req domain.UpdateTransactionVerifiedReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) h.mongoLoggerSvc.Info("Failed to parse UpdateTransactionVerified request",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
h.logger.Info("Update Transaction Verified", slog.Bool("verified", req.Verified))
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate UpdateTransactionVerified",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
zap.String("errMsg", errMsg),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, role, companyID, branchID) err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, role, companyID, branchID)
if err != nil { if err != nil {
h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) h.mongoLoggerSvc.Error("Failed to update transaction verification",
zap.Int64("transactionID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification")
} }

View File

@ -1,12 +1,15 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
// CreateTicket godoc // CreateTicket godoc
@ -19,29 +22,51 @@ import (
// @Success 200 {object} domain.CreateTicketRes // @Success 200 {object} domain.CreateTicketRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /ticket [post] // @Router /api/v1/ticket [post]
func (h *Handler) CreateTicket(c *fiber.Ctx) error { func (h *Handler) CreateTicket(c *fiber.Ctx) error {
var req domain.CreateTicketReq var req domain.CreateTicketReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse CreateTicket request", "error", err) h.mongoLoggerSvc.Info("Failed to parse CreateTicket request",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate CreateTicketReq",
zap.Any("CreateTicketReq", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("error", errMsg),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
newTicket, rows, err := h.ticketSvc.CreateTicket(c.Context(), req, c.IP()) newTicket, rows, err := h.ticketSvc.CreateTicket(c.Context(), req, c.IP())
if err != nil { if err != nil {
switch err {
case ticket.ErrEventHasBeenRemoved, ticket.ErrTicketHasExpired, var statusCode int
ticket.ErrRawOddInvalid, ticket.ErrTooManyOutcomesForTicket,
ticket.ErrTicketAmountTooHigh, ticket.ErrTicketLimitForSingleUser, if isTicketError := h.ticketSvc.CheckTicketError(err); isTicketError {
ticket.ErrTicketWinningTooHigh: statusCode = fiber.StatusBadRequest
return fiber.NewError(fiber.StatusBadRequest, err.Error()) } else {
statusCode = fiber.StatusInternalServerError
} }
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
h.mongoLoggerSvc.Error("Failed to create ticket",
zap.Any("req", req),
zap.Int("status_code", statusCode),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(statusCode, err.Error())
} }
res := domain.CreateTicketRes{ res := domain.CreateTicketRes{
FastCode: newTicket.ID, FastCode: newTicket.ID,
@ -61,18 +86,28 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
// @Success 200 {object} domain.TicketRes // @Success 200 {object} domain.TicketRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /ticket/{id} [get] // @Router /api/v1/ticket/{id} [get]
func (h *Handler) GetTicketByID(c *fiber.Ctx) error { func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
ticketID := c.Params("id") ticketID := c.Params("id")
id, err := strconv.ParseInt(ticketID, 10, 64) id, err := strconv.ParseInt(ticketID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid ticket ID", "ticketID", ticketID, "error", err) h.mongoLoggerSvc.Info("Invalid ticket ID",
zap.String("ticketID", ticketID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid ticket ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid ticket ID")
} }
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id) ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err) h.mongoLoggerSvc.Info("Failed to get ticket by ID",
zap.Int64("ticketID", id),
zap.Int("status_code", fiber.StatusNotFound),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket") return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
} }
@ -94,12 +129,16 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
// @Success 200 {array} domain.TicketRes // @Success 200 {array} domain.TicketRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /ticket [get] // @Router /api/v1/ticket [get]
func (h *Handler) GetAllTickets(c *fiber.Ctx) error { func (h *Handler) GetAllTickets(c *fiber.Ctx) error {
tickets, err := h.ticketSvc.GetAllTickets(c.Context()) tickets, err := h.ticketSvc.GetAllTickets(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to get tickets", "error", err) h.mongoLoggerSvc.Error("Failed to get tickets",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets")
} }

View File

@ -127,7 +127,7 @@ type CreateRefillReq struct {
// @Success 200 {object} TransferWalletRes // @Success 200 {object} TransferWalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /transfer/wallet/{id} [get] // @Router /api/v1/transfer/wallet/{id} [get]
func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error { func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error {
walletID := c.Params("id") walletID := c.Params("id")
@ -135,14 +135,24 @@ func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error {
id, err := strconv.ParseInt(walletID, 10, 64) id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) h.mongoLoggerSvc.Error("Invalid wallet ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) zap.String("walletID", walletID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
} }
transfers, err := h.walletSvc.GetTransfersByWallet(c.Context(), int64(id)) transfers, err := h.walletSvc.GetTransfersByWallet(c.Context(), int64(id))
if err != nil { if err != nil {
h.logger.Error("Failed to get transfers by wallet", "walletID", walletID, "error", err) h.mongoLoggerSvc.Error("Failed to get transfers by wallet",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transfers", err, nil) zap.String("walletID", walletID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
var transferResponses []TransferWalletRes var transferResponses []TransferWalletRes
@ -164,7 +174,7 @@ func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error {
// @Success 200 {object} TransferWalletRes // @Success 200 {object} TransferWalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /transfer/wallet/:id [post] // @Router /api/v1/transfer/wallet/:id [post]
func (h *Handler) TransferToWallet(c *fiber.Ctx) error { func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
receiverIDString := c.Params("id") receiverIDString := c.Params("id")
@ -172,59 +182,100 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) receiverID, err := strconv.ParseInt(receiverIDString, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) h.mongoLoggerSvc.Info("Invalid wallet ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) zap.Int64("walletID", receiverID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
} }
// Get sender ID from the cashier // Get sender ID from the cashier
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
fmt.Printf("\n\nCompany ID: %v\n\n", companyID.Value) // fmt.Printf("\n\nCompany ID: %v\n\n", companyID.Value)
var senderID int64 var senderID int64
//TODO: check to make sure that the cashiers aren't transferring TO branch wallet //TODO: check to make sure that the cashiers aren't transferring TO branch wallet
switch role { switch role {
case domain.RoleCustomer: case domain.RoleCustomer:
h.logger.Error("Unauthorized access", "userID", userID, "role", role) h.mongoLoggerSvc.Error("Unauthorized access",
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(role)),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Unauthorized access")
case domain.RoleAdmin: case domain.RoleAdmin:
company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value) company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ h.mongoLoggerSvc.Error("Failed to fetch company",
Message: "Failed to fetch company", zap.Int64("userID", userID),
Error: err.Error(), zap.Int("status_code", fiber.StatusInternalServerError),
}) zap.Error(err),
// return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching company", err, nil) zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
senderID = company.WalletID senderID = company.WalletID
h.logger.Error("Will", "userID", userID, "role", role) // h.logger.Error("Will", "userID", userID, "role", role)
case domain.RoleSuperAdmin: case domain.RoleSuperAdmin:
return response.WriteJSON(c, fiber.StatusBadRequest, "Super Admin does not have a wallet", err, nil) return fiber.NewError(fiber.StatusBadRequest, "Super Admin does not have a wallet")
case domain.RoleBranchManager: case domain.RoleBranchManager:
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch Manager does not have a wallet", err, nil) return fiber.NewError(fiber.StatusBadRequest, "Branch Manager does not have a wallet")
case domain.RoleCashier: case domain.RoleCashier:
cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get branch", "user ID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get branch by cashier",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil) zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
senderID = cashierBranch.WalletID senderID = cashierBranch.WalletID
default: default:
return response.WriteJSON(c, fiber.StatusInternalServerError, "Unknown Role", err, nil) h.mongoLoggerSvc.Error("Unknown Role",
zap.Int64("userID", userID),
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Unknown Role")
} }
var req CreateTransferReq var req CreateTransferReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateTransferReq failed", "error", err) h.mongoLoggerSvc.Error("CreateTransferReq failed to parse body",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("Failed to validate CreateTransferReq",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
transfer, err := h.walletSvc.TransferToWallet(c.Context(), transfer, err := h.walletSvc.TransferToWallet(c.Context(),
@ -234,8 +285,13 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
) )
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to transfer money to wallet", zap.Error(err)) h.mongoLoggerSvc.Error("Failed to transfer money to wallet",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertTransfer(transfer) res := convertTransfer(transfer)
@ -243,7 +299,6 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil)
} }
// RefillWallet godoc // RefillWallet godoc
// @Summary Refill wallet // @Summary Refill wallet
// @Description Super Admin route to refill a wallet // @Description Super Admin route to refill a wallet
@ -254,7 +309,7 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
// @Success 200 {object} TransferWalletRes // @Success 200 {object} TransferWalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /transfer/refill/:id [post] // @Router /api/v1/transfer/refill/:id [post]
func (h *Handler) RefillWallet(c *fiber.Ctx) error { func (h *Handler) RefillWallet(c *fiber.Ctx) error {
receiverIDString := c.Params("id") receiverIDString := c.Params("id")
@ -263,21 +318,42 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error {
receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) receiverID, err := strconv.ParseInt(receiverIDString, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) h.mongoLoggerSvc.Error("Invalid wallet ID",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) zap.Int64("walletID", receiverID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
} }
// Get sender ID from the cashier // Get sender ID from the cashier
var req CreateRefillReq var req CreateRefillReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateRefillReq failed", "error", err) h.mongoLoggerSvc.Info("CreateRefillReq failed to parse CreateRefillReq",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate CreateRefillReq",
zap.Int64("userID", userID),
zap.String("errMsg", errMsg),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
transfer, err := h.walletSvc.AddToWallet( transfer, err := h.walletSvc.AddToWallet(
@ -287,7 +363,15 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error {
}, domain.TRANSFER_BANK, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet directly by super-admin", req.Amount)) }, domain.TRANSFER_BANK, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet directly by super-admin", req.Amount))
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) h.mongoLoggerSvc.Error("Creating Transfer Failed",
zap.Int64("userID", userID),
zap.Float32("Amount", req.Amount),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := convertTransfer(transfer) res := convertTransfer(transfer)

View File

@ -11,6 +11,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CheckPhoneEmailExistReq struct { type CheckPhoneEmailExistReq struct {
@ -32,23 +33,36 @@ type CheckPhoneEmailExistRes struct {
// @Success 200 {object} CheckPhoneEmailExistRes // @Success 200 {object} CheckPhoneEmailExistRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/checkPhoneEmailExist [post] // @Router /api/v1/user/checkPhoneEmailExist [post]
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
var req CheckPhoneEmailExistReq var req CheckPhoneEmailExistReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse CheckPhoneEmailExist request", "error", err) h.mongoLoggerSvc.Info("Failed to parse CheckPhoneEmailExist request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") zap.Int("status_code", fiber.StatusInternalServerError),
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 { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email) emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
if err != nil { if err != nil {
h.logger.Error("Failed to check phone/email existence", "error", err) h.mongoLoggerSvc.Error("Failed to check phone/email existence",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence") zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence:"+err.Error())
} }
res := CheckPhoneEmailExistRes{ res := CheckPhoneEmailExistRes{
@ -73,17 +87,25 @@ type RegisterCodeReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post] // @Router /api/v1/user/sendRegisterCode [post]
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
var req RegisterCodeReq var req RegisterCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse SendRegisterCode request", "error", err) h.mongoLoggerSvc.Info("Failed to parse SendRegisterCode request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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 { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
var sentTo string var sentTo string
@ -99,8 +121,14 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
} }
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, "twilio"); err != nil { if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, "twilio"); err != nil {
h.logger.Error("Failed to send register code", "error", err) h.mongoLoggerSvc.Error("Failed to send register code",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") zap.String("Medium", string(medium)),
zap.String("Send To", string(sentTo)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
@ -126,17 +154,25 @@ type RegisterUserReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/register [post] // @Router /api/v1/user/register [post]
func (h *Handler) RegisterUser(c *fiber.Ctx) error { func (h *Handler) RegisterUser(c *fiber.Ctx) error {
var req RegisterUserReq var req RegisterUserReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse RegisterUser request", "error", err) h.mongoLoggerSvc.Info("Failed to parse RegisterUser request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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 { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
user := domain.RegisterUserReq{ user := domain.RegisterUserReq{
FirstName: req.FirstName, FirstName: req.FirstName,
@ -150,7 +186,13 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
} }
medium, err := getMedium(req.Email, req.PhoneNumber) medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil { if err != nil {
h.logger.Error("RegisterUser failed", "error", err) h.mongoLoggerSvc.Info("Failed to get medium",
zap.String("email", req.Email),
zap.String("phone number", req.PhoneNumber),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
@ -170,20 +212,36 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
if errors.Is(err, domain.ErrOtpNotFound) { if errors.Is(err, domain.ErrOtpNotFound) {
return fiber.NewError(fiber.StatusBadRequest, "User already exist") return fiber.NewError(fiber.StatusBadRequest, "User already exist")
} }
h.logger.Error("RegisterUser failed", "error", err) h.mongoLoggerSvc.Error("Failed to register user",
return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error") zap.String("email", req.Email),
zap.String("phone number", req.PhoneNumber),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "failed to register user:"+err.Error())
} }
newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID) newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID)
if err != nil { if err != nil {
h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err) h.mongoLoggerSvc.Error("Failed to create wallet for user",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet") zap.Int64("userID", newUser.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet:"+err.Error())
} }
if req.ReferalCode != "" { if req.ReferalCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode) err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode)
if err != nil { if err != nil {
h.logger.Warn("Failed to process referral during registration", "phone", req.PhoneNumber, "code", req.ReferalCode, "error", err) h.mongoLoggerSvc.Error("Failed to process referral during registration",
zap.String("phone", req.PhoneNumber),
zap.String("code", req.ReferalCode),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
} }
} }
@ -193,8 +251,13 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
"Added 100.0 to wallet only as test for deployment") "Added 100.0 to wallet only as test for deployment")
if err != nil { if err != nil {
h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err) h.mongoLoggerSvc.Error("Failed to update wallet for user",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet") zap.Int64("userID", newUser.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
@ -216,17 +279,25 @@ type ResetCodeReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post] // @Router /api/v1/user/sendResetCode [post]
func (h *Handler) SendResetCode(c *fiber.Ctx) error { func (h *Handler) SendResetCode(c *fiber.Ctx) error {
var req ResetCodeReq var req ResetCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse SendResetCode request", "error", err) h.mongoLoggerSvc.Info("Failed to parse SendResetCode request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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 { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
var sentTo string var sentTo string
@ -238,13 +309,24 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
sentTo = req.PhoneNumber sentTo = req.PhoneNumber
medium = domain.OtpMediumSms medium = domain.OtpMediumSms
} else { } else {
h.mongoLoggerSvc.Info("Email or PhoneNumber must be provided",
zap.String("Email", req.Email),
zap.String("Phone Number", req.PhoneNumber),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
} }
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, "twilio"); err != nil { if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, "twilio"); err != nil {
h.logger.Error("Failed to send reset code", "error", err) h.mongoLoggerSvc.Error("Failed to send reset code",
fmt.Println(err) zap.String("medium", string(medium)),
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") zap.String("sentTo", string(sentTo)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
@ -267,22 +349,36 @@ type ResetPasswordReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post] // @Router /api/v1/user/resetPassword [post]
func (h *Handler) ResetPassword(c *fiber.Ctx) error { func (h *Handler) ResetPassword(c *fiber.Ctx) error {
var req ResetPasswordReq var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse ResetPassword request", "error", err) h.mongoLoggerSvc.Info("Failed to parse ResetPassword request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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 { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
medium, err := getMedium(req.Email, req.PhoneNumber) medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil { if err != nil {
h.logger.Error("Failed to determine medium for ResetPassword", "error", err) h.mongoLoggerSvc.Info("Failed to determine medium for ResetPassword",
zap.String("Email", req.Email),
zap.String("Phone Number", req.PhoneNumber),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
@ -295,8 +391,13 @@ func (h *Handler) ResetPassword(c *fiber.Ctx) error {
} }
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil { if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
h.logger.Error("Failed to reset password", "error", err) h.mongoLoggerSvc.Error("Failed to reset password",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password") zap.Any("userID", resetReq),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
@ -328,26 +429,40 @@ type UserProfileRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security Bearer // @Security Bearer
// @Router /user/profile [get] // @Router /api/v1/user/profile [get]
func (h *Handler) UserProfile(c *fiber.Ctx) error { func (h *Handler) UserProfile(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Error("Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") 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) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get user profile", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get user profile",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve 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) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve 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 lastLogin = &user.CreatedAt
@ -396,37 +511,51 @@ type SearchUserByNameOrPhoneReq struct {
// @Success 200 {object} UserProfileRes // @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/search [post] // @Router /api/v1/user/search [post]
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// TODO: Add filtering by role based on which user is calling this // TODO: Add filtering by role based on which user is calling this
var req SearchUserByNameOrPhoneReq var req SearchUserByNameOrPhoneReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("SearchUserByNameOrPhone failed", "error", err) h.mongoLoggerSvc.Error("Failed to Search UserBy Name Or Phone failed",
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ zap.Any("request", req),
"error": "Invalid 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())
} }
valErrs, ok := h.validator.Validate(c, req) valErrs, ok := h.validator.Validate(c, req)
if !ok { if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
return nil for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID) users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID)
if err != nil { if err != nil {
h.logger.Error("SearchUserByNameOrPhone failed", "error", err) h.mongoLoggerSvc.Error("Failed to get user by name or phone",
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ zap.Any("request", req),
"error": "Internal server error", zap.Int("status_code", fiber.StatusBadRequest),
}) zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "failed to get users"+err.Error())
} }
var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) var res []UserProfileRes = make([]UserProfileRes, 0, len(users))
for _, user := range users { for _, user := range users {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Any("userID", user.ID),
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 lastLogin = &user.CreatedAt
@ -462,7 +591,7 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/single/{id} [get] // @Router /api/v1/user/single/{id} [get]
func (h *Handler) GetUserByID(c *fiber.Ctx) error { func (h *Handler) GetUserByID(c *fiber.Ctx) error {
// branchId := int64(12) //c.Locals("branch_id").(int64) // branchId := int64(12) //c.Locals("branch_id").(int64)
// filter := user.Filter{ // filter := user.Filter{
@ -482,21 +611,36 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error {
userIDstr := c.Params("id") userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64) userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("failed to fetch user using UserID", "error", err) h.mongoLoggerSvc.Info("failed to parse user id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid user id")
} }
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Get User By ID failed", "error", err) h.mongoLoggerSvc.Error("Get User By ID failed",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) 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 get cashiers:"+err.Error())
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { if err != nil {
if err != authentication.ErrRefreshTokenNotFound { if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err) h.mongoLoggerSvc.Error("Failed to get user last login",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login") zap.Int64("userID", user.ID),
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 lastLogin = &user.CreatedAt
@ -532,19 +676,29 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/delete/{id} [delete] // @Router /api/v1/user/delete/{id} [delete]
func (h *Handler) DeleteUser(c *fiber.Ctx) error { func (h *Handler) DeleteUser(c *fiber.Ctx) error {
userIDstr := c.Params("id") userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64) userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil { if err != nil {
h.logger.Error("DeleteUser failed", "error", err) h.mongoLoggerSvc.Info("Failed to parse user id",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid user ID", nil, nil) zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
} }
err = h.userSvc.DeleteUser(c.Context(), userID) err = h.userSvc.DeleteUser(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to delete user", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to delete user",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to delete user", nil, nil) 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 delete user:"+err.Error())
} }
return response.WriteJSON(c, fiber.StatusOK, "User deleted successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "User deleted successfully", nil, nil)
@ -569,14 +723,17 @@ type UpdateUserSuspendRes struct {
// @Success 200 {object} UpdateUserSuspendRes // @Success 200 {object} UpdateUserSuspendRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/suspend [post] // @Router /api/v1/user/suspend [post]
func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error { func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
var req UpdateUserSuspendReq var req UpdateUserSuspendReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse UpdateUserSuspend request", "error", err) h.mongoLoggerSvc.Info("Failed to parse UpdateUserSuspend request",
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} }
fmt.Printf("user suspended %v \n", req)
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
var errMsg string var errMsg string
for field, msg := range valErrs { for field, msg := range valErrs {
@ -587,8 +744,13 @@ func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended) err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended)
if err != nil { if err != nil {
h.logger.Error("Failed to update user suspend status", "error", err) h.mongoLoggerSvc.Error("Failed to update user suspend status",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user suspend status") zap.Any("UpdateUserSuspendReq", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user suspend status:"+err.Error())
} }
res := UpdateUserSuspendRes{ res := UpdateUserSuspendRes{
@ -607,18 +769,27 @@ func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
// @Success 200 {array} domain.BetRes // @Success 200 {array} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/bets [get] // @Router /api/v1/user/bets [get]
func (h *Handler) GetBetByUserID(c *fiber.Ctx) error { func (h *Handler) GetBetByUserID(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") 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") return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
bets, err := h.betSvc.GetBetByUserID(c.Context(), userID) bets, err := h.betSvc.GetBetByUserID(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get bets", "error", err) h.mongoLoggerSvc.Error("Failed to get bets",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets") 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 bets:"+err.Error())
} }
res := make([]domain.BetRes, len(bets)) res := make([]domain.BetRes, len(bets))

View File

@ -1,12 +1,14 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type WalletRes struct { type WalletRes struct {
@ -94,18 +96,28 @@ type BranchWalletRes struct {
// @Success 200 {object} WalletRes // @Success 200 {object} WalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [get] // @Router /api/v1/wallet/{id} [get]
func (h *Handler) GetWalletByID(c *fiber.Ctx) error { func (h *Handler) GetWalletByID(c *fiber.Ctx) error {
walletID := c.Params("id") walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64) id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) h.mongoLoggerSvc.Error("Invalid wallet ID",
zap.String("walletID", walletID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
} }
wallet, err := h.walletSvc.GetWalletByID(c.Context(), id) wallet, err := h.walletSvc.GetWalletByID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get wallet by ID", "walletID", id, "error", err) h.mongoLoggerSvc.Error("Failed to get wallet by ID",
zap.Int64("walletID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
} }
@ -123,14 +135,18 @@ func (h *Handler) GetWalletByID(c *fiber.Ctx) error {
// @Success 200 {array} WalletRes // @Success 200 {array} WalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /wallet [get] // @Router /api/v1/wallet [get]
func (h *Handler) GetAllWallets(c *fiber.Ctx) error { func (h *Handler) GetAllWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllWallets(c.Context()) wallets, err := h.walletSvc.GetAllWallets(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to get wallets", "error", err) h.mongoLoggerSvc.Error("Failed to get wallets",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
var res []WalletRes = make([]WalletRes, 0, len(wallets)) var res []WalletRes = make([]WalletRes, 0, len(wallets))
@ -152,14 +168,18 @@ func (h *Handler) GetAllWallets(c *fiber.Ctx) error {
// @Success 200 {array} WalletRes // @Success 200 {array} WalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /branchWallet [get] // @Router /api/v1/branchWallet [get]
func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error { func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllBranchWallets(c.Context()) wallets, err := h.walletSvc.GetAllBranchWallets(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to get wallets", "error", err) h.mongoLoggerSvc.Error("Failed to get wallets",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallets")
} }
var res []BranchWalletRes = make([]BranchWalletRes, 0, len(wallets)) var res []BranchWalletRes = make([]BranchWalletRes, 0, len(wallets))
@ -191,14 +211,18 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
// @Success 200 {array} CustomerWalletRes // @Success 200 {array} CustomerWalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /customerWallet [get] // @Router /api/v1/customerWallet [get]
func (h *Handler) GetAllCustomerWallets(c *fiber.Ctx) error { func (h *Handler) GetAllCustomerWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllCustomerWallet(c.Context()) wallets, err := h.walletSvc.GetAllCustomerWallet(c.Context())
if err != nil { if err != nil {
h.logger.Error("Failed to get wallets", "error", err) h.mongoLoggerSvc.Error("Failed to get customer wallets",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallets")
} }
var res []CustomerWalletRes = make([]CustomerWalletRes, 0, len(wallets)) var res []CustomerWalletRes = make([]CustomerWalletRes, 0, len(wallets))
@ -225,29 +249,53 @@ type UpdateWalletActiveReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [patch] // @Router /api/v1/wallet/{id} [patch]
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error { func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
walletID := c.Params("id") walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64) id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) h.mongoLoggerSvc.Info("Invalid wallet ID",
zap.String("walletID", walletID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
} }
var req UpdateWalletActiveReq var req UpdateWalletActiveReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse UpdateWalletActive request", "error", err) h.mongoLoggerSvc.Info("Failed to parse UpdateWalletActive request",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate UpdateWalletActiveReq",
zap.String("errMsg", errMsg),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err = h.walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive) err = h.walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive)
if err != nil { if err != nil {
h.logger.Error("Failed to update wallet active status", "walletID", id, "error", err) h.mongoLoggerSvc.Error("Failed to update wallet active status",
zap.Int64("walletID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update wallet") return fiber.NewError(fiber.StatusInternalServerError, "Failed to update wallet")
} }
@ -265,24 +313,37 @@ func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
// @Success 200 {object} CustomerWalletRes // @Success 200 {object} CustomerWalletRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/wallet [get] // @Router /api/v1/user/wallet [get]
func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error { func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.mongoLoggerSvc.Info("Invalid user ID in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id in context")
} }
role, ok := c.Locals("role").(domain.Role) role, ok := c.Locals("role").(domain.Role)
if !ok { if !ok {
h.logger.Error("Invalid role in context", "userID", userID) h.mongoLoggerSvc.Error("Invalid role in context",
return fiber.NewError(fiber.StatusUnauthorized, "Invalid role") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.String("role", string(role)),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid role")
} }
if role != domain.RoleCustomer { if role != domain.RoleCustomer {
h.logger.Error("Unauthorized access", "userID", userID, "role", role) h.mongoLoggerSvc.Error("Unauthorized access",
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized access") zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(role)),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Unauthorized access")
} }
// companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64) // companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
@ -295,7 +356,12 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID) wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
if err != nil { if err != nil {
h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err) h.mongoLoggerSvc.Error("Failed to get customer wallet",
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 wallet") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
} }
@ -315,40 +381,63 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashierWallet [get] // @Router /api/v1/cashierWallet [get]
func (h *Handler) GetWalletForCashier(c *fiber.Ctx) error { func (h *Handler) GetWalletForCashier(c *fiber.Ctx) error {
cashierID, ok := c.Locals("user_id").(int64) cashierID, ok := c.Locals("user_id").(int64)
if !ok || cashierID == 0 { if !ok || cashierID == 0 {
h.logger.Error("Invalid cashier ID in context") h.mongoLoggerSvc.Error("Invalid cashier ID in context",
return response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid cashier identification", nil, nil) zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid cashier id")
} }
role, ok := c.Locals("role").(domain.Role) role, ok := c.Locals("role").(domain.Role)
if !ok || role != domain.RoleCashier { if !ok || role != domain.RoleCashier {
h.logger.Error("Unauthorized access", "cashierID", cashierID, "role", role) h.mongoLoggerSvc.Error("Unauthorized access",
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(role)),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Unauthorized access")
} }
branchID, ok := c.Locals("branch_id").(domain.ValidInt64) branchID, ok := c.Locals("branch_id").(domain.ValidInt64)
if !ok || !branchID.Valid { if !ok || !branchID.Valid {
h.logger.Error("Invalid branch ID in context", "cashierID", cashierID) h.mongoLoggerSvc.Info("Invalid branch ID in context",
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", nil, nil) zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid branch ID")
} }
branch, err := h.branchSvc.GetBranchByID(c.Context(), branchID.Value) branch, err := h.branchSvc.GetBranchByID(c.Context(), branchID.Value)
if err != nil { if err != nil {
h.logger.Error("Failed to get branch by ID", "branchID", branchID.Value, "error", err) h.mongoLoggerSvc.Error("Failed to get branch by ID",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch", err, nil) zap.Int64("branchID", branchID.Value),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
wallet, err := h.walletSvc.GetWalletByID(c.Context(), branch.WalletID) wallet, err := h.walletSvc.GetWalletByID(c.Context(), branch.WalletID)
if err != nil { if err != nil {
h.logger.Error("Failed to get wallet for cashier", "cashierID", cashierID, "error", err) h.mongoLoggerSvc.Error("Failed to get wallet for cashier",
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil) zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} }
res := WalletRes{ res := WalletRes{

View File

@ -4,22 +4,38 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
func (a *App) authMiddleware(c *fiber.Ctx) error { func (a *App) authMiddleware(c *fiber.Ctx) error {
ip := c.IP()
userAgent := c.Get("User-Agent")
c.Locals("ip_address", ip)
c.Locals("user_agent", userAgent)
authHeader := c.Get("Authorization") authHeader := c.Get("Authorization")
if authHeader == "" { if authHeader == "" {
// fmt.Println("Auth Header Missing") a.mongoLoggerSvc.Info("Authorization header missing",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing") return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing")
} }
if !strings.HasPrefix(authHeader, "Bearer ") { if !strings.HasPrefix(authHeader, "Bearer ") {
fmt.Println("Invalid authorization header format") a.mongoLoggerSvc.Info("Invalid authorization header format",
zap.String("authHeader", authHeader),
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid authorization header format") return fiber.NewError(fiber.StatusUnauthorized, "Invalid authorization header format")
} }
@ -28,10 +44,20 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
claim, err := jwtutil.ParseJwt(accessToken, a.JwtConfig.JwtAccessKey) claim, err := jwtutil.ParseJwt(accessToken, a.JwtConfig.JwtAccessKey)
if err != nil { if err != nil {
if errors.Is(err, jwtutil.ErrExpiredToken) { if errors.Is(err, jwtutil.ErrExpiredToken) {
fmt.Println("Token Expired") a.mongoLoggerSvc.Info("Access Token Expired",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Access token expired") return fiber.NewError(fiber.StatusUnauthorized, "Access token expired")
} }
fmt.Println("Invalid Token") a.mongoLoggerSvc.Info("Invalid Access Token",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token")
} }
@ -44,7 +70,12 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
} }
// Asserting to make sure that there is no company role without a valid company id // Asserting to make sure that there is no company role without a valid company id
if claim.Role != domain.RoleSuperAdmin && claim.Role != domain.RoleCustomer && !claim.CompanyID.Valid { if claim.Role != domain.RoleSuperAdmin && claim.Role != domain.RoleCustomer && !claim.CompanyID.Valid {
fmt.Println("Company Role without Company ID") a.mongoLoggerSvc.Error("Company Role without Company ID",
zap.Int64("userID", claim.UserId),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Company Role without Company ID") return fiber.NewError(fiber.StatusInternalServerError, "Company Role without Company ID")
} }
c.Locals("user_id", claim.UserId) c.Locals("user_id", claim.UserId)
@ -57,8 +88,13 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
if claim.Role == domain.RoleCashier { if claim.Role == domain.RoleCashier {
branch, err := a.branchSvc.GetBranchByCashier(c.Context(), claim.UserId) branch, err := a.branchSvc.GetBranchByCashier(c.Context(), claim.UserId)
if err != nil { if err != nil {
a.logger.Error("Failed to get branch id for bet", "error", err) a.mongoLoggerSvc.Error("Failed to get branch id for cashier",
return fiber.NewError(fiber.StatusInternalServerError, "Failed to branch id for bet") zap.Int64("userID", claim.UserId),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to branch id for cashier")
} }
branchID = domain.ValidInt64{ branchID = domain.ValidInt64{
Value: branch.ID, Value: branch.ID,
@ -72,61 +108,116 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
} }
func (a *App) SuperAdminOnly(c *fiber.Ctx) error { func (a *App) SuperAdminOnly(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole != domain.RoleSuperAdmin { if userRole != domain.RoleSuperAdmin {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") a.mongoLoggerSvc.Warn("Attempt to access restricted SuperAdminOnly route",
zap.Int64("userID", userID),
zap.String("role", string(userRole)),
zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
} }
return c.Next() return c.Next()
} }
func (a *App) CompanyOnly(c *fiber.Ctx) error { func (a *App) CompanyOnly(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole == domain.RoleCustomer { if userRole == domain.RoleCustomer {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") a.mongoLoggerSvc.Warn("Attempt to access restricted CompanyOnly route",
zap.Int64("userID", userID),
zap.String("role", string(userRole)),
zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
} }
return c.Next() return c.Next()
} }
func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error { func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin { if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") a.mongoLoggerSvc.Warn("Attempt to access restricted OnlyAdminAndAbove route",
zap.Int64("userID", userID),
zap.String("role", string(userRole)),
zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
} }
return c.Next() return c.Next()
} }
func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error { func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin && userRole != domain.RoleBranchManager { if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin && userRole != domain.RoleBranchManager {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") a.mongoLoggerSvc.Warn("Attempt to access restricted OnlyBranchMangerAndAbove route",
zap.Int64("userID", userID),
zap.String("role", string(userRole)),
zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
} }
return c.Next() return c.Next()
} }
func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error { func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
tokenStr := c.Query("token") tokenStr := c.Query("token")
ip := c.IP()
userAgent := c.Get("User-Agent")
if tokenStr == "" { if tokenStr == "" {
a.logger.Error("Missing token in query parameter") a.mongoLoggerSvc.Info("Missing token in query parameter",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Missing token") return fiber.NewError(fiber.StatusUnauthorized, "Missing token")
} }
claim, err := jwtutil.ParseJwt(tokenStr, a.JwtConfig.JwtAccessKey) claim, err := jwtutil.ParseJwt(tokenStr, a.JwtConfig.JwtAccessKey)
if err != nil { if err != nil {
if errors.Is(err, jwtutil.ErrExpiredToken) { if errors.Is(err, jwtutil.ErrExpiredToken) {
a.logger.Error("Token expired") a.mongoLoggerSvc.Info("Token expired",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Token expired") return fiber.NewError(fiber.StatusUnauthorized, "Token expired")
} }
a.logger.Error("Invalid token", "error", err) a.logger.Error("Invalid token", "error", err)
a.mongoLoggerSvc.Info("Invalid token",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid token") return fiber.NewError(fiber.StatusUnauthorized, "Invalid token")
} }
userID := claim.UserId userID := claim.UserId
if userID == 0 { if userID == 0 {
a.logger.Error("Invalid user ID in token claims") a.mongoLoggerSvc.Info("Invalid user ID in token claims",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID") return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID")
} }
c.Locals("userID", userID) c.Locals("userID", userID)
a.logger.Info("Authenticated WebSocket connection", "userID", userID) a.mongoLoggerSvc.Info("Authenticated WebSocket connection",
zap.Int64("userID", userID),
zap.Time("timestamp", time.Now()),
)
return c.Next() return c.Next()
} }

View File

@ -52,26 +52,27 @@ func (a *App) initAppRoutes() {
a.mongoLoggerSvc, a.mongoLoggerSvc,
) )
group := a.fiber.Group("/api/v1")
a.fiber.Get("/", func(c *fiber.Ctx) error { a.fiber.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"message": "Welcome to the FortuneBet API", "message": "Welcome to the FortuneBet API",
"version": "1.0dev8.5", "version": "1.0dev10",
}) })
}) })
// Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
group.Get("/", func(c *fiber.Ctx) error { groupV1 := a.fiber.Group("/api/v1")
groupV1.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"message": "FortuneBet API V1 pre-alpha", "message": "FortuneBet API V1 pre-alpha",
"version": "1.0dev9", "version": "1.0dev10",
}) })
}) })
// Auth Routes // Auth Routes
a.fiber.Post("/auth/login", h.LoginCustomer) groupV1.Post("/auth/login", h.LoginCustomer)
a.fiber.Post("/auth/refresh", h.RefreshToken) groupV1.Post("/auth/refresh", h.RefreshToken)
a.fiber.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer) groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error { groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok { if !ok {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID") return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID")
@ -98,149 +99,146 @@ func (a *App) initAppRoutes() {
}) })
// User Routes // User Routes
a.fiber.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/resetPassword", h.ResetPassword)
a.fiber.Post("/user/sendResetCode", h.SendResetCode) groupV1.Post("/user/sendResetCode", h.SendResetCode)
a.fiber.Post("/user/register", h.RegisterUser) groupV1.Post("/user/register", h.RegisterUser)
a.fiber.Post("/user/sendRegisterCode", h.SendRegisterCode) groupV1.Post("/user/sendRegisterCode", h.SendRegisterCode)
a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist) groupV1.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile) groupV1.Get("/user/profile", a.authMiddleware, h.UserProfile)
a.fiber.Get("/user/single/:id", a.authMiddleware, h.GetUserByID) groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
a.fiber.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser) groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
a.fiber.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend) groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
a.fiber.Get("/user/ress", a.authMiddleware, h.GetBetByUserID) groupV1.Get("/user/bets", a.authMiddleware, h.GetBetByUserID)
a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet) groupV1.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone) groupV1.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
// Referral Routes // Referral Routes
a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferralCode) groupV1.Post("/referral/create", a.authMiddleware, h.CreateReferralCode)
a.fiber.Get("/referral/stats", a.authMiddleware, h.GetReferralStats) groupV1.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
a.fiber.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings) groupV1.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings)
a.fiber.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings) groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings)
a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
// Bonus Routes // Bonus Routes
a.fiber.Get("/bonus", a.authMiddleware, h.GetBonusMultiplier) groupV1.Get("/bonus", a.authMiddleware, h.GetBonusMultiplier)
a.fiber.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier) groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier)
a.fiber.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier) groupV1.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier)
a.fiber.Get("/cashiers", a.authMiddleware, h.GetAllCashiers) groupV1.Get("/cashiers", a.authMiddleware, h.GetAllCashiers)
a.fiber.Get("/cashiers/:id", a.authMiddleware, h.GetCashierByID) groupV1.Get("/cashiers/:id", a.authMiddleware, h.GetCashierByID)
a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier) groupV1.Post("/cashiers", a.authMiddleware, h.CreateCashier)
a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier) groupV1.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier)
a.fiber.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers) groupV1.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers)
a.fiber.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID) groupV1.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID)
a.fiber.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer) groupV1.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer)
a.fiber.Get("/admin", a.authMiddleware, h.GetAllAdmins) groupV1.Get("/admin", a.authMiddleware, h.GetAllAdmins)
a.fiber.Get("/admin/:id", a.authMiddleware, h.GetAdminByID) groupV1.Get("/admin/:id", a.authMiddleware, h.GetAdminByID)
a.fiber.Post("/admin", a.authMiddleware, h.CreateAdmin) groupV1.Post("/admin", a.authMiddleware, h.CreateAdmin)
a.fiber.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin) groupV1.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin)
a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers) groupV1.Get("/managers", a.authMiddleware, h.GetAllManagers)
a.fiber.Get("/managers/:id", a.authMiddleware, h.GetManagerByID) groupV1.Get("/managers/:id", a.authMiddleware, h.GetManagerByID)
a.fiber.Post("/managers", a.authMiddleware, h.CreateManager) groupV1.Post("/managers", a.authMiddleware, h.CreateManager)
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers) groupV1.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID) groupV1.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
a.fiber.Get("/odds", h.GetALLPrematchOdds) groupV1.Get("/odds", h.GetALLPrematchOdds)
a.fiber.Get("/odds/upcoming/:upcoming_id", h.GetOddsByUpcomingID) groupV1.Get("/odds/upcoming/:upcoming_id", h.GetOddsByUpcomingID)
a.fiber.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID) groupV1.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
a.fiber.Get("/events", h.GetAllUpcomingEvents) groupV1.Get("/events", h.GetAllUpcomingEvents)
a.fiber.Get("/events/:id", h.GetUpcomingEventByID) groupV1.Get("/events/:id", h.GetUpcomingEventByID)
a.fiber.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved) groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
a.fiber.Get("/top-leagues", h.GetTopLeagues) groupV1.Get("/top-leagues", h.GetTopLeagues)
a.fiber.Get("/events/:id/flag", h.UpdateEventFlagged) groupV1.Get("/events/:id/flag", h.UpdateEventFlagged)
// Leagues // Leagues
a.fiber.Get("/leagues", h.GetAllLeagues) groupV1.Get("/leagues", h.GetAllLeagues)
a.fiber.Put("/leagues/:id/set-active", h.SetLeagueActive) groupV1.Put("/leagues/:id/set-active", h.SetLeagueActive)
groupV1.Put("/leagues/:id/featured", h.SetLeagueFeatured)
a.fiber.Get("/result/:id", h.GetResultsByEventID) groupV1.Get("/result/:id", h.GetResultsByEventID)
// Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
// Branch // Branch
a.fiber.Post("/branch", a.authMiddleware, h.CreateBranch) groupV1.Post("/branch", a.authMiddleware, h.CreateBranch)
a.fiber.Get("/branch", a.authMiddleware, h.GetAllBranches) groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches)
a.fiber.Get("/branch/:id", a.authMiddleware, h.GetBranchByID) groupV1.Get("/branch/:id", a.authMiddleware, h.GetBranchByID)
a.fiber.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID) groupV1.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID)
a.fiber.Put("/branch/:id", a.authMiddleware, h.UpdateBranch) groupV1.Put("/branch/:id", a.authMiddleware, h.UpdateBranch)
a.fiber.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus) groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
a.fiber.Put("/branch/:id/set-inactive", a.authMiddleware, h.UpdateBranchStatus) groupV1.Put("/branch/:id/set-inactive", a.authMiddleware, h.UpdateBranchStatus)
a.fiber.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch) groupV1.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch)
a.fiber.Get("/search/branch", a.authMiddleware, h.SearchBranch) groupV1.Get("/search/branch", a.authMiddleware, h.SearchBranch)
// /branch/search // /branch/search
// branch/wallet // branch/wallet
a.fiber.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers) groupV1.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers)
a.fiber.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier) groupV1.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier)
// Branch Operation // Branch Operation
a.fiber.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations) groupV1.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations)
a.fiber.Post("/supportedOperation", a.authMiddleware, h.CreateSupportedOperation) groupV1.Post("/supportedOperation", a.authMiddleware, h.CreateSupportedOperation)
a.fiber.Post("/operation", a.authMiddleware, h.CreateBranchOperation) groupV1.Post("/operation", a.authMiddleware, h.CreateBranchOperation)
a.fiber.Get("/branch/:id/operation", a.authMiddleware, h.GetBranchOperations) groupV1.Get("/branch/:id/operation", a.authMiddleware, h.GetBranchOperations)
a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, h.DeleteBranchOperation) groupV1.Delete("/branch/:id/operation/:opID", a.authMiddleware, h.DeleteBranchOperation)
// Company // Company
a.fiber.Post("/company", a.authMiddleware, a.SuperAdminOnly, h.CreateCompany) groupV1.Post("/company", a.authMiddleware, a.SuperAdminOnly, h.CreateCompany)
a.fiber.Get("/company", a.authMiddleware, a.SuperAdminOnly, h.GetAllCompanies) groupV1.Get("/company", a.authMiddleware, a.SuperAdminOnly, h.GetAllCompanies)
a.fiber.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCompanyByID) groupV1.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCompanyByID)
a.fiber.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany) groupV1.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany)
a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany) groupV1.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany)
a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID) groupV1.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID)
a.fiber.Get("/search/company", a.authMiddleware, h.SearchCompany) groupV1.Get("/search/company", a.authMiddleware, h.SearchCompany)
a.fiber.Get("/admin-company", a.authMiddleware, h.GetCompanyForAdmin) groupV1.Get("/admin-company", a.authMiddleware, h.GetCompanyForAdmin)
// Ticket Routes // Ticket Routes
a.fiber.Post("/ticket", h.CreateTicket) groupV1.Post("/ticket", h.CreateTicket)
a.fiber.Get("/ticket", h.GetAllTickets) groupV1.Get("/ticket", h.GetAllTickets)
a.fiber.Get("/ticket/:id", h.GetTicketByID) groupV1.Get("/ticket/:id", h.GetTicketByID)
// Bet Routes // Bet Routes
a.fiber.Post("/sport/bet", a.authMiddleware, h.CreateBet) groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet)
a.fiber.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode) groupV1.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
a.fiber.Get("/sport/bet", a.authMiddleware, h.GetAllBet) groupV1.Get("/sport/bet", a.authMiddleware, h.GetAllBet)
a.fiber.Get("/sport/bet/:id", h.GetBetByID) groupV1.Get("/sport/bet/:id", h.GetBetByID)
a.fiber.Get("/sport/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID) groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)
a.fiber.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut) groupV1.Delete("/sport/bet/:id", a.authMiddleware, h.DeleteBet)
a.fiber.Delete("/sport/bet/:id", a.authMiddleware, h.DeleteBet)
a.fiber.Post("/sport/random/bet", a.authMiddleware, h.RandomBet) groupV1.Post("/sport/random/bet", a.authMiddleware, h.RandomBet)
// Wallet // Wallet
a.fiber.Get("/wallet", h.GetAllWallets) groupV1.Get("/wallet", h.GetAllWallets)
a.fiber.Get("/wallet/:id", h.GetWalletByID) groupV1.Get("/wallet/:id", h.GetWalletByID)
a.fiber.Put("/wallet/:id", h.UpdateWalletActive) groupV1.Put("/wallet/:id", h.UpdateWalletActive)
a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets) groupV1.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets)
a.fiber.Get("/customerWallet", a.authMiddleware, h.GetAllCustomerWallets) groupV1.Get("/customerWallet", a.authMiddleware, h.GetAllCustomerWallets)
a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier) groupV1.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
// Transfer // Transfer
// /transfer/wallet - transfer from one wallet to another wallet // /transfer/wallet - transfer from one wallet to another wallet
a.fiber.Post("/transfer/wallet/:id", a.authMiddleware, h.TransferToWallet) groupV1.Post("/transfer/wallet/:id", a.authMiddleware, h.TransferToWallet)
a.fiber.Get("/transfer/wallet/:id", a.authMiddleware, h.GetTransfersByWallet) groupV1.Get("/transfer/wallet/:id", a.authMiddleware, h.GetTransfersByWallet)
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet) groupV1.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
//Chapa Routes //Chapa Routes
group.Post("/chapa/payments/webhook/verify", h.WebhookCallback) a.fiber.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
group.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction) a.fiber.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
group.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit) a.fiber.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
group.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal) a.fiber.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
group.Get("/chapa/banks", h.GetSupportedBanks) a.fiber.Get("/chapa/banks", h.GetSupportedBanks)
// Currencies // Currencies
group.Get("/currencies", h.GetSupportedCurrencies) groupV1.Get("/currencies", h.GetSupportedCurrencies)
group.Get("/currencies/convert", h.ConvertCurrency) groupV1.Get("/currencies/convert", h.ConvertCurrency)
//Report Routes //Report Routes
group.Get("/reports/dashboard", h.GetDashboardReport) groupV1.Get("/reports/dashboard", h.GetDashboardReport)
group.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile) groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile)
group.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
//Wallet Monitor Service //Wallet Monitor Service
// group.Get("/debug/wallet-monitor/status", func(c *fiber.Ctx) error { // group.Get("/debug/wallet-monitor/status", func(c *fiber.Ctx) error {
@ -263,48 +261,48 @@ func (a *App) initAppRoutes() {
// group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer) // group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
//Alea Play Virtual Game Routes //Alea Play Virtual Game Routes
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame) groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
group.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback) groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
//Veli Virtual Game Routes //Veli Virtual Game Routes
group.Post("/veli/providers", h.GetProviders) groupV1.Post("/veli/providers", h.GetProviders)
group.Post("/veli/games-list", h.GetGamesByProvider) groupV1.Post("/veli/games-list", h.GetGamesByProvider)
group.Post("/veli/start-game", a.authMiddleware, h.StartGame) groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame)
group.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame) groupV1.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame)
a.fiber.Post("/balance", h.GetBalance) a.fiber.Post("/balance", h.GetBalance)
// a.fiber.Post("/bet", h.PlaceBet) // a.fiber.Post("/bet", h.PlaceBet)
// a.fiber.Post("/win", h.RegisterWin) // a.fiber.Post("/win", h.RegisterWin)
// a.fiber.Post("/cancel", h.CancelTransaction) // a.fiber.Post("/cancel", h.CancelTransaction)
group.Post("/veli/gaming-activity", h.GetGamingActivity) groupV1.Post("/veli/gaming-activity", h.GetGamingActivity)
//mongoDB logs //mongoDB logs
ctx := context.Background() ctx := context.Background()
group.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(ctx)) groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(ctx))
// Recommendation Routes // Recommendation Routes
// group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) // group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations)
// Transactions /shop/transactions // Transactions /shop/transactions
a.fiber.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet) groupV1.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet)
a.fiber.Get("/shop/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByBetID) groupV1.Get("/shop/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByBetID)
a.fiber.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet) groupV1.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
a.fiber.Post("/shop/bet/:id/generate", a.authMiddleware, a.CompanyOnly, h.CashoutBet) groupV1.Post("/shop/bet/:id/generate", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
a.fiber.Get("/shop/cashout/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByCashoutID) groupV1.Get("/shop/cashout/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByCashoutID)
a.fiber.Post("/shop/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutByCashoutID) groupV1.Post("/shop/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutByCashoutID)
a.fiber.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer) groupV1.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
// a.fiber.Get("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer) // groupV1.Get("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
a.fiber.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions) groupV1.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions)
a.fiber.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID) groupV1.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID)
a.fiber.Get("/shop/transaction/:id/bet", a.authMiddleware, h.GetShopBetByTransactionID) groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, h.GetShopBetByTransactionID)
a.fiber.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) groupV1.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
// Notification Routes // Notification Routes
a.fiber.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket) groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
a.fiber.Get("/notifications", a.authMiddleware, h.GetNotifications) groupV1.Get("/notifications", a.authMiddleware, h.GetNotifications)
a.fiber.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications) groupV1.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
a.fiber.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead) groupV1.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead)
a.fiber.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications) groupV1.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications)
a.fiber.Post("/notifications/create", a.authMiddleware, h.CreateAndSendNotification) groupV1.Post("/notifications/create", a.authMiddleware, h.CreateAndSendNotification)
// Virtual Game Routes // Virtual Game Routes
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame) a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
@ -317,21 +315,14 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/tournamentWin ", h.HandleTournamentWin) a.fiber.Post("/tournamentWin ", h.HandleTournamentWin)
a.fiber.Get("/popok/games", h.GetGameList) a.fiber.Get("/popok/games", h.GetGameList)
a.fiber.Get("/popok/games/recommend", a.authMiddleware, h.RecommendGames) a.fiber.Get("/popok/games/recommend", a.authMiddleware, h.RecommendGames)
group.Post("/virtual-game/favorites", a.authMiddleware, h.AddFavorite) groupV1.Post("/virtual-game/favorites", a.authMiddleware, h.AddFavorite)
group.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite) groupV1.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
group.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites) groupV1.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
//Issue Reporting Routes //Issue Reporting Routes
group.Post("/issues", a.authMiddleware, h.CreateIssue) //anyone who has logged can report a groupV1.Post("/issues", a.authMiddleware, h.CreateIssue) //anyone who has logged can report a
group.Get("/issues/customer/:customer_id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetUserIssues) groupV1.Get("/issues/customer/:customer_id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetUserIssues)
group.Get("/issues", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllIssues) groupV1.Get("/issues", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllIssues)
group.Patch("/issues/:issue_id/status", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateIssueStatus) groupV1.Patch("/issues/:issue_id/status", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateIssueStatus)
group.Delete("/issues/:issue_id", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteIssue) groupV1.Delete("/issues/:issue_id", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteIssue)
} }
///user/profile get
// @Router /user/resetPassword [post]
// @Router /user/sendResetCode [post]
// @Router /user/register [post]
// @Router /user/sendRegisterCode [post]
// @Router /user/checkPhoneEmailExist [post]

View File

@ -56,6 +56,8 @@ restore:
restore_file: restore_file:
@echo "Restoring latest backup..." @echo "Restoring latest backup..."
gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
seed_data:
cat db/data/seed_data.sql | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
postgres_log: postgres_log:
docker logs fortunebet-backend-postgres-1 docker logs fortunebet-backend-postgres-1
.PHONY: swagger .PHONY: swagger