Merge branch 'ticket-bet'

This commit is contained in:
Samuel Tariku 2025-07-13 03:49:26 +03:00
commit 5614958c5b
71 changed files with 17576 additions and 13029 deletions

View File

@ -2,6 +2,10 @@
"cSpell.words": [
"Cashout",
"narg",
"notificationservice",
"sqlc"
]
],
"cSpell.enabledFileTypes": {
"sql": false
}
}

View File

@ -125,7 +125,7 @@ func main() {
companySvc := company.NewService(store)
leagueSvc := league.New(store)
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, 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)
bonusSvc := bonus.NewService(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

@ -45,24 +45,14 @@ CREATE TABLE IF NOT EXISTS bets (
amount BIGINT NOT NULL,
total_odds REAL NOT NULL,
status INT NOT NULL,
full_name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
company_id BIGINT,
branch_id BIGINT,
user_id BIGINT,
cashed_out BOOLEAN DEFAULT FALSE NOT NULL,
cashout_id VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_id BIGINT NOT NULL,
is_shop_bet BOOLEAN NOT NULL,
cashed_out BOOLEAN NOT NULL DEFAULT false,
outcomes_hash TEXT NOT NULL,
fast_code VARCHAR(10) NOT NULL,
processed BOOLEAN DEFAULT FALSE NOT NULL,
UNIQUE(cashout_id),
CHECK (
user_id IS NOT NULL
OR branch_id IS NOT NULL
)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tickets (
id BIGSERIAL PRIMARY KEY,
@ -217,14 +207,16 @@ CREATE TABLE IF NOT EXISTS shop_deposits (
CREATE TABLE IF NOT EXISTS branches (
id BIGSERIAL PRIMARY KEY,
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,
wallet_id BIGINT NOT NULL,
branch_manager_id BIGINT NOT NULL,
company_id BIGINT NOT NULL,
is_self_owned BOOLEAN NOT NULL DEFAULT false,
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 (
id BIGSERIAL PRIMARY KEY,
@ -239,6 +231,10 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
branch_id BIGINT NOT NULL,
UNIQUE(user_id, branch_id)
);
CREATE TABLE IF NOT EXISTS branch_locations (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE events (
id TEXT PRIMARY KEY,
sport_id INT,
@ -261,7 +257,8 @@ CREATE TABLE events (
is_live BOOLEAN,
status TEXT,
fetched_at TIMESTAMP DEFAULT now(),
source TEXT DEFAULT 'b365api'
source TEXT DEFAULT 'b365api',
flagged BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE odds (
id SERIAL PRIMARY KEY,
@ -289,6 +286,8 @@ CREATE TABLE companies (
name TEXT NOT NULL,
admin_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,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
@ -324,7 +323,7 @@ CREATE TABLE bonus (
CREATE VIEW companies_details AS
SELECT companies.*,
wallets.balance,
wallets.is_active,
wallets.is_active as wallet_is_active,
users.first_name AS admin_first_name,
users.last_name AS admin_last_name,
users.phone_number AS admin_phone_number
@ -348,10 +347,16 @@ CREATE TABLE IF NOT EXISTS supported_operations (
);
CREATE VIEW bet_with_outcomes AS
SELECT bets.*,
CONCAT(users.first_name, ' ', users.last_name) AS full_name,
users.phone_number,
JSON_AGG(bet_outcomes.*) AS outcomes
FROM bets
LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id
GROUP BY bets.id;
LEFT JOIN users ON bets.user_id = users.id
GROUP BY bets.id,
users.first_name,
users.last_name,
users.phone_number;
CREATE VIEW ticket_with_outcomes AS
SELECT tickets.*,
JSON_AGG(ticket_outcomes.*) AS outcomes
@ -439,8 +444,7 @@ ADD CONSTRAINT unique_email UNIQUE (email),
ALTER TABLE refresh_tokens
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE bets
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE wallets
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id),
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
@ -463,7 +467,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);
ALTER TABLE branches
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
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;

View File

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

View File

@ -1,12 +1,14 @@
CREATE TABLE IF NOT EXISTS reported_issues (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL,
user_id BIGINT NOT NULL REFERENCES users(id),
user_role VARCHAR(255) NOT NULL,
subject TEXT NOT NULL,
description TEXT NOT NULL,
issue_type TEXT NOT NULL, -- e.g., "deposit", "withdrawal", "bet", "technical"
status TEXT NOT NULL DEFAULT 'pending', -- pending, in_progress, resolved, rejected
issue_type TEXT NOT NULL,
-- e.g., "deposit", "withdrawal", "bet", "technical"
status TEXT NOT NULL DEFAULT 'pending',
-- pending, in_progress, resolved, rejected
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

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;

View File

@ -3,17 +3,12 @@ INSERT INTO bets (
amount,
total_odds,
status,
full_name,
phone_number,
branch_id,
user_id,
is_shop_bet,
cashout_id,
company_id,
outcomes_hash,
fast_code
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *;
-- name: CreateBetOutcome :copyfrom
INSERT INTO bet_outcomes (
@ -50,14 +45,6 @@ VALUES (
SELECT *
FROM bet_with_outcomes
wHERE (
branch_id = sqlc.narg('branch_id')
OR sqlc.narg('branch_id') IS NULL
)
AND (
company_id = sqlc.narg('company_id')
OR sqlc.narg('company_id') IS NULL
)
AND (
user_id = sqlc.narg('user_id')
OR sqlc.narg('user_id') IS NULL
)
@ -65,6 +52,10 @@ wHERE (
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') || '%'
@ -82,14 +73,6 @@ wHERE (
SELECT *
FROM bet_with_outcomes
WHERE id = $1;
-- name: GetBetByCashoutID :one
SELECT *
FROM bet_with_outcomes
WHERE cashout_id = $1;
-- name: GetBetByBranchID :many
SELECT *
FROM bet_with_outcomes
WHERE branch_id = $1;
-- name: GetBetByUserID :many
SELECT *
FROM bet_with_outcomes

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 (
name,
admin_id,
wallet_id
wallet_id,
deducted_percentage
)
VALUES ($1, $2, $3)
VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: GetAllCompanies :many
SELECT *
@ -36,6 +37,11 @@ WHERE name ILIKE '%' || $1 || '%';
UPDATE companies
SET name = COALESCE(sqlc.narg(name), name),
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
WHERE id = $1
RETURNING *;

View File

@ -126,45 +126,14 @@ SELECT id
FROM events
WHERE is_live = true;
-- name: GetAllUpcomingEvents :many
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
source,
fetched_at
SELECT *
FROM events
WHERE start_time > now()
AND is_live = false
AND status = 'upcoming'
ORDER BY start_time ASC;
-- name: GetExpiredUpcomingEvents :many
SELECT events.id,
events.sport_id,
events.match_name,
events.home_team,
events.away_team,
events.home_team_id,
events.away_team_id,
events.home_kit_image,
events.away_kit_image,
events.league_id,
events.league_name,
events.start_time,
events.is_live,
events.status,
events.source,
events.fetched_at,
SELECT events.*,
leagues.country_code as league_cc
FROM events
LEFT JOIN leagues ON leagues.id = league_id
@ -199,24 +168,13 @@ WHERE is_live = false
AND (
leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
flagged = sqlc.narg('flagged')
OR sqlc.narg('flagged') IS NULL
);
-- name: GetPaginatedUpcomingEvents :many
SELECT events.id,
events.sport_id,
events.match_name,
events.home_team,
events.away_team,
events.home_team_id,
events.away_team_id,
events.home_kit_image,
events.away_kit_image,
events.league_id,
events.league_name,
events.start_time,
events.is_live,
events.status,
events.source,
events.fetched_at,
SELECT events.*,
leagues.country_code as league_cc
FROM events
LEFT JOIN leagues ON leagues.id = league_id
@ -243,26 +201,14 @@ WHERE start_time > now()
leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
flagged = sqlc.narg('flagged')
OR sqlc.narg('flagged') IS NULL
)
ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetUpcomingByID :one
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
source,
fetched_at
SELECT *
FROM events
WHERE id = $1
AND is_live = false
@ -271,9 +217,12 @@ LIMIT 1;
-- name: UpdateMatchResult :exec
UPDATE events
SET score = $1,
status = $2,
fetched_at = NOW()
status = $2
WHERE id = $3;
-- name: UpdateFlagged :exec
UPDATE events
SET flagged = $1
WHERE id = $2;
-- name: DeleteEvent :exec
DELETE FROM events
WHERE id = $1;

View File

@ -1,32 +1,37 @@
-- name: CreateReportedIssue :one
INSERT INTO reported_issues (
customer_id, subject, description, issue_type, metadata
) VALUES (
$1, $2, $3, $4, $5
)
user_id,
user_role,
subject,
description,
issue_type,
metadata
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *;
-- name: ListReportedIssues :many
SELECT * FROM reported_issues
SELECT *
FROM reported_issues
ORDER BY created_at DESC
LIMIT $1 OFFSET $2;
-- name: ListReportedIssuesByCustomer :many
SELECT * FROM reported_issues
WHERE customer_id = $1
-- name: ListReportedIssuesByUser :many
SELECT *
FROM reported_issues
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3;
-- name: CountReportedIssues :one
SELECT COUNT(*) FROM reported_issues;
-- name: CountReportedIssuesByCustomer :one
SELECT COUNT(*) FROM reported_issues WHERE customer_id = $1;
SELECT COUNT(*)
FROM reported_issues;
-- name: CountReportedIssuesByUser :one
SELECT COUNT(*)
FROM reported_issues
WHERE user_id = $1;
-- name: UpdateReportedIssueStatus :exec
UPDATE reported_issues
SET status = $2, updated_at = NOW()
SET status = $2,
updated_at = NOW()
WHERE id = $1;
-- name: DeleteReportedIssue :exec
DELETE FROM reported_issues WHERE id = $1;
DELETE FROM reported_issues
WHERE id = $1;

View File

@ -1,44 +1,59 @@
-- 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
SELECT
b.company_id,
c.name AS company_name,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
FROM bets b
JOIN companies c ON b.company_id = c.id
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.company_id, c.name;
-- name: GetBranchWiseReport :many
SELECT
b.branch_id,
br.name AS branch_name,
br.company_id,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
FROM bets b
JOIN branches br ON b.branch_id = br.id
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.branch_id, br.name, br.company_id;
-- name: GetCompanyWiseReport :many
SELECT b.company_id,
c.name AS company_name,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(
SUM(
CASE
WHEN b.cashed_out THEN b.amount
ELSE 0
END
),
0
) AS total_cash_out,
COALESCE(
SUM(
CASE
WHEN b.status = 5 THEN b.amount
ELSE 0
END
),
0
) AS total_cash_backs
FROM shop_bet_detail b
JOIN companies c ON b.company_id = c.id
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.company_id,
c.name;
-- name: GetBranchWiseReport :many
SELECT b.branch_id,
br.name AS branch_name,
br.company_id,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(
SUM(
CASE
WHEN b.cashed_out THEN b.amount
ELSE 0
END
),
0
) AS total_cash_out,
COALESCE(
SUM(
CASE
WHEN b.status = 5 THEN b.amount
ELSE 0
END
),
0
) AS total_cash_backs
FROM shop_bet_detail b
JOIN branches br ON b.branch_id = br.id
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.branch_id,
br.name,
br.company_id;

View File

@ -11,3 +11,25 @@ VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value
RETURNING *;
-- name: SetInitialData :exec
INSERT INTO settings (key, value)
VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('bet_amount_limit', '100000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('daily_ticket_limit', '50') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;
INSERT INTO settings (key, value)
VALUES ('amount_for_bet_referral', '1000000') ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value;

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

View File

@ -16,13 +16,8 @@ INSERT INTO bets (
amount,
total_odds,
status,
full_name,
phone_number,
branch_id,
user_id,
is_shop_bet,
cashout_id,
company_id,
outcomes_hash,
fast_code
)
@ -31,18 +26,13 @@ RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, b
`
type CreateBetParams struct {
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
CashoutID string `json:"cashout_id"`
CompanyID pgtype.Int8 `json:"company_id"`
OutcomesHash string `json:"outcomes_hash"`
FastCode string `json:"fast_code"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"`
UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
OutcomesHash string `json:"outcomes_hash"`
FastCode string `json:"fast_code"`
}
func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) {
@ -50,13 +40,8 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
arg.Amount,
arg.TotalOdds,
arg.Status,
arg.FullName,
arg.PhoneNumber,
arg.BranchID,
arg.UserID,
arg.IsShopBet,
arg.CashoutID,
arg.CompanyID,
arg.OutcomesHash,
arg.FastCode,
)
@ -66,16 +51,9 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.CashedOut,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
@ -123,41 +101,36 @@ const GetAllBets = `-- name: GetAllBets :many
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, fast_code, processed, outcomes
FROM bet_with_outcomes
wHERE (
branch_id = $1
user_id = $1
OR $1 IS NULL
)
AND (
company_id = $2
is_shop_bet = $2
OR $2 IS NULL
)
AND (
user_id = $3
cashed_out = $3
OR $3 IS NULL
)
AND (
is_shop_bet = $4
full_name ILIKE '%' || $4 || '%'
OR phone_number ILIKE '%' || $4 || '%'
OR $4 IS NULL
)
AND (
full_name ILIKE '%' || $5 || '%'
OR phone_number ILIKE '%' || $5 || '%'
created_at > $5
OR $5 IS NULL
)
AND (
created_at > $6
created_at < $6
OR $6 IS NULL
)
AND (
created_at < $7
OR $7 IS NULL
)
`
type GetAllBetsParams struct {
BranchID pgtype.Int8 `json:"branch_id"`
CompanyID pgtype.Int8 `json:"company_id"`
UserID pgtype.Int8 `json:"user_id"`
IsShopBet pgtype.Bool `json:"is_shop_bet"`
CashedOut pgtype.Bool `json:"cashed_out"`
Query pgtype.Text `json:"query"`
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
@ -165,10 +138,9 @@ type GetAllBetsParams struct {
func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) {
rows, err := q.db.Query(ctx, GetAllBets,
arg.BranchID,
arg.CompanyID,
arg.UserID,
arg.IsShopBet,
arg.CashedOut,
arg.Query,
arg.CreatedBefore,
arg.CreatedAfter,
@ -185,16 +157,9 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.CashedOut,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
@ -302,16 +267,9 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.CashedOut,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
@ -334,16 +292,9 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.CashedOut,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
@ -358,7 +309,7 @@ FROM bet_with_outcomes
WHERE user_id = $1
`
func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]BetWithOutcome, error) {
func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOutcome, error) {
rows, err := q.db.Query(ctx, GetBetByUserID, userID)
if err != nil {
return nil, err
@ -372,16 +323,9 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.CashedOut,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
@ -400,13 +344,14 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
const GetBetCount = `-- name: GetBetCount :one
SELECT COUNT(*)
FROM bets
WHERE user_id = $1
WHERE user_id = $1
AND outcomes_hash = $2
`
type GetBetCountParams struct {
UserID pgtype.Int8 `json:"user_id"`
OutcomesHash string `json:"outcomes_hash"`
UserID int64 `json:"user_id"`
OutcomesHash string `json:"outcomes_hash"`
}
func (q *Queries) GetBetCount(ctx context.Context, arg GetBetCountParams) (int64, error) {

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 (
name,
admin_id,
wallet_id
wallet_id,
deducted_percentage
)
VALUES ($1, $2, $3)
RETURNING id, name, admin_id, wallet_id, created_at, updated_at
VALUES ($1, $2, $3, $4)
RETURNING id, name, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at
`
type CreateCompanyParams struct {
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
DeductedPercentage float32 `json:"deducted_percentage"`
}
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
err := row.Scan(
&i.ID,
&i.Name,
&i.AdminID,
&i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
)
@ -52,7 +61,7 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
}
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
WHERE (
name ILIKE '%' || $1 || '%'
@ -91,10 +100,12 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
&i.Name,
&i.AdminID,
&i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.Balance,
&i.IsActive,
&i.WalletIsActive,
&i.AdminFirstName,
&i.AdminLastName,
&i.AdminPhoneNumber,
@ -110,7 +121,7 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
}
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
WHERE id = $1
`
@ -123,10 +134,12 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
&i.Name,
&i.AdminID,
&i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.Balance,
&i.IsActive,
&i.WalletIsActive,
&i.AdminFirstName,
&i.AdminLastName,
&i.AdminPhoneNumber,
@ -135,7 +148,7 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
}
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
WHERE name ILIKE '%' || $1 || '%'
`
@ -154,10 +167,12 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text)
&i.Name,
&i.AdminID,
&i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.Balance,
&i.IsActive,
&i.WalletIsActive,
&i.AdminFirstName,
&i.AdminLastName,
&i.AdminPhoneNumber,
@ -176,25 +191,40 @@ const UpdateCompany = `-- name: UpdateCompany :one
UPDATE companies
SET name = COALESCE($2, name),
admin_id = COALESCE($3, admin_id),
is_active = COALESCE($4, is_active),
deducted_percentage = COALESCE(
$5,
deducted_percentage
),
updated_at = CURRENT_TIMESTAMP
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 {
ID int64 `json:"id"`
Name pgtype.Text `json:"name"`
AdminID pgtype.Int8 `json:"admin_id"`
ID int64 `json:"id"`
Name pgtype.Text `json:"name"`
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) {
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
err := row.Scan(
&i.ID,
&i.Name,
&i.AdminID,
&i.WalletID,
&i.DeductedPercentage,
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
)

View File

@ -22,23 +22,7 @@ func (q *Queries) DeleteEvent(ctx context.Context, id string) error {
}
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
source,
fetched_at
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged
FROM events
WHERE start_time > now()
AND is_live = false
@ -46,35 +30,15 @@ WHERE start_time > now()
ORDER BY start_time ASC
`
type GetAllUpcomingEventsRow struct {
ID string `json:"id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEventsRow, error) {
func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
rows, err := q.db.Query(ctx, GetAllUpcomingEvents)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllUpcomingEventsRow
var items []Event
for rows.Next() {
var i GetAllUpcomingEventsRow
var i Event
if err := rows.Scan(
&i.ID,
&i.SportID,
@ -89,10 +53,16 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
&i.Source,
&i.Flagged,
); err != nil {
return nil, err
}
@ -105,22 +75,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve
}
const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many
SELECT events.id,
events.sport_id,
events.match_name,
events.home_team,
events.away_team,
events.home_team_id,
events.away_team_id,
events.home_kit_image,
events.away_kit_image,
events.league_id,
events.league_name,
events.start_time,
events.is_live,
events.status,
events.source,
events.fetched_at,
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged,
leagues.country_code as league_cc
FROM events
LEFT JOIN leagues ON leagues.id = league_id
@ -144,12 +99,19 @@ type GetExpiredUpcomingEventsRow struct {
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
Score pgtype.Text `json:"score"`
MatchMinute pgtype.Int4 `json:"match_minute"`
TimerStatus pgtype.Text `json:"timer_status"`
AddedTime pgtype.Int4 `json:"added_time"`
MatchPeriod pgtype.Int4 `json:"match_period"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
LeagueCc pgtype.Text `json:"league_cc"`
Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"`
}
func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Text) ([]GetExpiredUpcomingEventsRow, error) {
@ -173,12 +135,19 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
&i.LeagueCc,
&i.Source,
&i.Flagged,
&i.LeagueCc_2,
); err != nil {
return nil, err
}
@ -191,22 +160,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
}
const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many
SELECT events.id,
events.sport_id,
events.match_name,
events.home_team,
events.away_team,
events.home_team_id,
events.away_team_id,
events.home_kit_image,
events.away_kit_image,
events.league_id,
events.league_name,
events.start_time,
events.is_live,
events.status,
events.source,
events.fetched_at,
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged,
leagues.country_code as league_cc
FROM events
LEFT JOIN leagues ON leagues.id = league_id
@ -233,8 +187,12 @@ WHERE start_time > now()
leagues.country_code = $5
OR $5 IS NULL
)
AND (
flagged = $6
OR $6 IS NULL
)
ORDER BY start_time ASC
LIMIT $7 OFFSET $6
LIMIT $8 OFFSET $7
`
type GetPaginatedUpcomingEventsParams struct {
@ -243,6 +201,7 @@ type GetPaginatedUpcomingEventsParams struct {
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"`
Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"`
}
@ -259,12 +218,19 @@ type GetPaginatedUpcomingEventsRow struct {
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
Score pgtype.Text `json:"score"`
MatchMinute pgtype.Int4 `json:"match_minute"`
TimerStatus pgtype.Text `json:"timer_status"`
AddedTime pgtype.Int4 `json:"added_time"`
MatchPeriod pgtype.Int4 `json:"match_period"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
LeagueCc pgtype.Text `json:"league_cc"`
Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"`
}
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
@ -274,6 +240,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.Flagged,
arg.Offset,
arg.Limit,
)
@ -296,12 +263,19 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
&i.LeagueCc,
&i.Source,
&i.Flagged,
&i.LeagueCc_2,
); err != nil {
return nil, err
}
@ -339,6 +313,10 @@ WHERE is_live = false
leagues.country_code = $5
OR $5 IS NULL
)
AND (
flagged = $6
OR $6 IS NULL
)
`
type GetTotalEventsParams struct {
@ -347,6 +325,7 @@ type GetTotalEventsParams struct {
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"`
}
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
@ -356,6 +335,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.Flagged,
)
var count int64
err := row.Scan(&count)
@ -363,23 +343,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
}
const GetUpcomingByID = `-- name: GetUpcomingByID :one
SELECT id,
sport_id,
match_name,
home_team,
away_team,
home_team_id,
away_team_id,
home_kit_image,
away_kit_image,
league_id,
league_name,
league_cc,
start_time,
is_live,
status,
source,
fetched_at
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged
FROM events
WHERE id = $1
AND is_live = false
@ -387,29 +351,9 @@ WHERE id = $1
LIMIT 1
`
type GetUpcomingByIDRow struct {
ID string `json:"id"`
SportID pgtype.Int4 `json:"sport_id"`
MatchName pgtype.Text `json:"match_name"`
HomeTeam pgtype.Text `json:"home_team"`
AwayTeam pgtype.Text `json:"away_team"`
HomeTeamID pgtype.Int4 `json:"home_team_id"`
AwayTeamID pgtype.Int4 `json:"away_team_id"`
HomeKitImage pgtype.Text `json:"home_kit_image"`
AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingByIDRow, error) {
func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (Event, error) {
row := q.db.QueryRow(ctx, GetUpcomingByID, id)
var i GetUpcomingByIDRow
var i Event
err := row.Scan(
&i.ID,
&i.SportID,
@ -424,10 +368,16 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingBy
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
&i.Source,
&i.Flagged,
)
return i, err
}
@ -673,11 +623,26 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) {
return items, nil
}
const UpdateFlagged = `-- name: UpdateFlagged :exec
UPDATE events
SET flagged = $1
WHERE id = $2
`
type UpdateFlaggedParams struct {
Flagged bool `json:"flagged"`
ID string `json:"id"`
}
func (q *Queries) UpdateFlagged(ctx context.Context, arg UpdateFlaggedParams) error {
_, err := q.db.Exec(ctx, UpdateFlagged, arg.Flagged, arg.ID)
return err
}
const UpdateMatchResult = `-- name: UpdateMatchResult :exec
UPDATE events
SET score = $1,
status = $2,
fetched_at = NOW()
status = $2
WHERE id = $3
`

View File

@ -10,7 +10,8 @@ import (
)
const CountReportedIssues = `-- name: CountReportedIssues :one
SELECT COUNT(*) FROM reported_issues
SELECT COUNT(*)
FROM reported_issues
`
func (q *Queries) CountReportedIssues(ctx context.Context) (int64, error) {
@ -20,12 +21,14 @@ func (q *Queries) CountReportedIssues(ctx context.Context) (int64, error) {
return count, err
}
const CountReportedIssuesByCustomer = `-- name: CountReportedIssuesByCustomer :one
SELECT COUNT(*) FROM reported_issues WHERE customer_id = $1
const CountReportedIssuesByUser = `-- name: CountReportedIssuesByUser :one
SELECT COUNT(*)
FROM reported_issues
WHERE user_id = $1
`
func (q *Queries) CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error) {
row := q.db.QueryRow(ctx, CountReportedIssuesByCustomer, customerID)
func (q *Queries) CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error) {
row := q.db.QueryRow(ctx, CountReportedIssuesByUser, userID)
var count int64
err := row.Scan(&count)
return count, err
@ -33,15 +36,20 @@ func (q *Queries) CountReportedIssuesByCustomer(ctx context.Context, customerID
const CreateReportedIssue = `-- name: CreateReportedIssue :one
INSERT INTO reported_issues (
customer_id, subject, description, issue_type, metadata
) VALUES (
$1, $2, $3, $4, $5
)
RETURNING id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at
user_id,
user_role,
subject,
description,
issue_type,
metadata
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
`
type CreateReportedIssueParams struct {
CustomerID int64 `json:"customer_id"`
UserID int64 `json:"user_id"`
UserRole string `json:"user_role"`
Subject string `json:"subject"`
Description string `json:"description"`
IssueType string `json:"issue_type"`
@ -50,7 +58,8 @@ type CreateReportedIssueParams struct {
func (q *Queries) CreateReportedIssue(ctx context.Context, arg CreateReportedIssueParams) (ReportedIssue, error) {
row := q.db.QueryRow(ctx, CreateReportedIssue,
arg.CustomerID,
arg.UserID,
arg.UserRole,
arg.Subject,
arg.Description,
arg.IssueType,
@ -59,7 +68,8 @@ func (q *Queries) CreateReportedIssue(ctx context.Context, arg CreateReportedIss
var i ReportedIssue
err := row.Scan(
&i.ID,
&i.CustomerID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
@ -72,7 +82,8 @@ func (q *Queries) CreateReportedIssue(ctx context.Context, arg CreateReportedIss
}
const DeleteReportedIssue = `-- name: DeleteReportedIssue :exec
DELETE FROM reported_issues WHERE id = $1
DELETE FROM reported_issues
WHERE id = $1
`
func (q *Queries) DeleteReportedIssue(ctx context.Context, id int64) error {
@ -81,7 +92,8 @@ func (q *Queries) DeleteReportedIssue(ctx context.Context, id int64) error {
}
const ListReportedIssues = `-- name: ListReportedIssues :many
SELECT id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at FROM reported_issues
SELECT id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
FROM reported_issues
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
@ -102,7 +114,8 @@ func (q *Queries) ListReportedIssues(ctx context.Context, arg ListReportedIssues
var i ReportedIssue
if err := rows.Scan(
&i.ID,
&i.CustomerID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
@ -121,21 +134,22 @@ func (q *Queries) ListReportedIssues(ctx context.Context, arg ListReportedIssues
return items, nil
}
const ListReportedIssuesByCustomer = `-- name: ListReportedIssuesByCustomer :many
SELECT id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at FROM reported_issues
WHERE customer_id = $1
const ListReportedIssuesByUser = `-- name: ListReportedIssuesByUser :many
SELECT id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
FROM reported_issues
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
type ListReportedIssuesByCustomerParams struct {
CustomerID int64 `json:"customer_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
type ListReportedIssuesByUserParams struct {
UserID int64 `json:"user_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListReportedIssuesByCustomer(ctx context.Context, arg ListReportedIssuesByCustomerParams) ([]ReportedIssue, error) {
rows, err := q.db.Query(ctx, ListReportedIssuesByCustomer, arg.CustomerID, arg.Limit, arg.Offset)
func (q *Queries) ListReportedIssuesByUser(ctx context.Context, arg ListReportedIssuesByUserParams) ([]ReportedIssue, error) {
rows, err := q.db.Query(ctx, ListReportedIssuesByUser, arg.UserID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
@ -145,7 +159,8 @@ func (q *Queries) ListReportedIssuesByCustomer(ctx context.Context, arg ListRepo
var i ReportedIssue
if err := rows.Scan(
&i.ID,
&i.CustomerID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
@ -166,7 +181,8 @@ func (q *Queries) ListReportedIssuesByCustomer(ctx context.Context, arg ListRepo
const UpdateReportedIssueStatus = `-- name: UpdateReportedIssueStatus :exec
UPDATE reported_issues
SET status = $2, updated_at = NOW()
SET status = $2,
updated_at = NOW()
WHERE id = $1
`

View File

@ -78,16 +78,9 @@ type Bet struct {
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"`
CashoutID string `json:"cashout_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
CashedOut bool `json:"cashed_out"`
OutcomesHash string `json:"outcomes_hash"`
FastCode string `json:"fast_code"`
Processed bool `json:"processed"`
@ -116,16 +109,9 @@ type BetWithOutcome struct {
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"`
CashoutID string `json:"cashout_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
CashedOut bool `json:"cashed_out"`
OutcomesHash string `json:"outcomes_hash"`
FastCode string `json:"fast_code"`
Processed bool `json:"processed"`
@ -183,26 +169,30 @@ type BranchOperation struct {
}
type CompaniesDetail struct {
ID int64 `json:"id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
Balance int64 `json:"balance"`
IsActive bool `json:"is_active"`
AdminFirstName string `json:"admin_first_name"`
AdminLastName string `json:"admin_last_name"`
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
ID int64 `json:"id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
DeductedPercentage float32 `json:"deducted_percentage"`
IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
Balance int64 `json:"balance"`
WalletIsActive bool `json:"wallet_is_active"`
AdminFirstName string `json:"admin_first_name"`
AdminLastName string `json:"admin_last_name"`
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
}
type Company struct {
ID int64 `json:"id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
ID int64 `json:"id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
DeductedPercentage float32 `json:"deducted_percentage"`
IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type CustomerWallet struct {
@ -254,6 +244,7 @@ type Event struct {
Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"`
}
type ExchangeRate struct {
@ -368,7 +359,8 @@ type RefreshToken struct {
type ReportedIssue struct {
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
UserID int64 `json:"user_id"`
UserRole string `json:"user_role"`
Subject string `json:"subject"`
Description string `json:"description"`
IssueType string `json:"issue_type"`

View File

@ -12,18 +12,35 @@ import (
)
const GetBranchWiseReport = `-- name: GetBranchWiseReport :many
SELECT
b.branch_id,
br.name AS branch_name,
br.company_id,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
FROM bets b
JOIN branches br ON b.branch_id = br.id
SELECT b.branch_id,
br.name AS branch_name,
br.company_id,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(
SUM(
CASE
WHEN b.cashed_out THEN b.amount
ELSE 0
END
),
0
) AS total_cash_out,
COALESCE(
SUM(
CASE
WHEN b.status = 5 THEN b.amount
ELSE 0
END
),
0
) AS total_cash_backs
FROM shop_bet_detail b
JOIN branches br ON b.branch_id = br.id
WHERE b.created_at BETWEEN $1 AND $2
GROUP BY b.branch_id, br.name, br.company_id
GROUP BY b.branch_id,
br.name,
br.company_id
`
type GetBranchWiseReportParams struct {
@ -32,7 +49,7 @@ type GetBranchWiseReportParams struct {
}
type GetBranchWiseReportRow struct {
BranchID pgtype.Int8 `json:"branch_id"`
BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
CompanyID int64 `json:"company_id"`
TotalBets int64 `json:"total_bets"`
@ -70,17 +87,33 @@ func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseRepo
}
const GetCompanyWiseReport = `-- name: GetCompanyWiseReport :many
SELECT
b.company_id,
c.name AS company_name,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
FROM bets b
JOIN companies c ON b.company_id = c.id
SELECT b.company_id,
c.name AS company_name,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(
SUM(
CASE
WHEN b.cashed_out THEN b.amount
ELSE 0
END
),
0
) AS total_cash_out,
COALESCE(
SUM(
CASE
WHEN b.status = 5 THEN b.amount
ELSE 0
END
),
0
) AS total_cash_backs
FROM shop_bet_detail b
JOIN companies c ON b.company_id = c.id
WHERE b.created_at BETWEEN $1 AND $2
GROUP BY b.company_id, c.name
GROUP BY b.company_id,
c.name
`
type GetCompanyWiseReportParams struct {
@ -89,7 +122,7 @@ type GetCompanyWiseReportParams struct {
}
type GetCompanyWiseReportRow struct {
CompanyID pgtype.Int8 `json:"company_id"`
CompanyID int64 `json:"company_id"`
CompanyName string `json:"company_name"`
TotalBets int64 `json:"total_bets"`
TotalCashMade interface{} `json:"total_cash_made"`
@ -123,77 +156,3 @@ func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseRe
}
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

@ -38,29 +38,23 @@ type CreateBetOutcome struct {
Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}
// If it is a ShopBet then UserID will be the cashier
// If it is a DigitalBet then UserID will be the user and the branchID will be 0 or nil
// If it is a ShopBet then UserID and Fullname will be the cashier
// If it is a DigitalBet then UserID and Fullname will be the user
type Bet struct {
ID int64
Amount Currency
TotalOdds float32
Status OutcomeStatus
FullName string
PhoneNumber string
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
IsShopBet bool
CashedOut bool
CashoutID string
FastCode string
CreatedAt time.Time
ID int64
Amount Currency
TotalOdds float32
Status OutcomeStatus
UserID int64
IsShopBet bool
CashedOut bool
FastCode string
CreatedAt time.Time
}
type BetFilter struct {
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
UserID ValidInt64
CashedOut ValidBool
IsShopBet ValidBool
Query ValidString
CreatedBefore ValidTime
@ -74,12 +68,9 @@ type GetBet struct {
Status OutcomeStatus
FullName string
PhoneNumber string
BranchID ValidInt64 // Can Be Nullable
CompanyID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
UserID int64
IsShopBet bool
CashedOut bool
CashoutID string
Outcomes []BetOutcome
FastCode string
CreatedAt time.Time
@ -89,13 +80,8 @@ type CreateBet struct {
Amount Currency
TotalOdds float32
Status OutcomeStatus
FullName string
PhoneNumber string
CompanyID ValidInt64 // Can Be Nullable
BranchID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable
UserID int64
IsShopBet bool
CashoutID string
OutcomesHash string
FastCode string
}
@ -109,8 +95,6 @@ type CreateBetOutcomeReq struct {
type CreateBetReq struct {
Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"`
Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"`
}
@ -124,28 +108,22 @@ type CreateBetRes struct {
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status OutcomeStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
BranchID int64 `json:"branch_id" example:"2"`
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CreatedNumber int64 `json:"created_number" example:"2"`
CashedID string `json:"cashed_id" example:"21234"`
}
type BetRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []BetOutcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status OutcomeStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
BranchID int64 `json:"branch_id" example:"2"`
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CashedOut bool `json:"cashed_out" example:"false"`
CashedID string `json:"cashed_id" example:"21234"`
CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"`
ID int64 `json:"id" example:"1"`
Outcomes []BetOutcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status OutcomeStatus `json:"status" example:"1"`
Fullname string `json:"full_name" example:"John Smith"`
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CashedOut bool `json:"cashed_out" example:"false"`
CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"`
FastCode string `json:"fast_code"`
}
func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
@ -154,29 +132,23 @@ func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
Amount: bet.Amount.Float32(),
TotalOdds: bet.TotalOdds,
Status: bet.Status,
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
UserID: bet.UserID,
CreatedNumber: createdNumber,
CashedID: bet.CashoutID,
}
}
func ConvertBet(bet GetBet) BetRes {
return BetRes{
ID: bet.ID,
Amount: bet.Amount.Float32(),
TotalOdds: bet.TotalOdds,
Status: bet.Status,
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
Outcomes: bet.Outcomes,
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
CashedID: bet.CashoutID,
CreatedAt: bet.CreatedAt,
ID: bet.ID,
Amount: bet.Amount.Float32(),
TotalOdds: bet.TotalOdds,
Status: bet.Status,
Fullname: bet.FullName,
UserID: bet.UserID,
Outcomes: bet.Outcomes,
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
CreatedAt: bet.CreatedAt,
FastCode: bet.FastCode,
}
}

View File

@ -74,3 +74,98 @@ type CreateBranchOperation struct {
BranchID 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
}
type ValidFloat32 struct {
Value float32
Valid bool
}
type ValidString struct {
Value string
Valid bool

View File

@ -1,42 +1,226 @@
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
// 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
type Company struct {
ID int64
Name string
AdminID int64
WalletID int64
ID int64
Name string
AdminID int64
WalletID int64
DeductedPercentage float32
IsActive bool
}
type CompanyFilter struct {
IsActive ValidBool
Query ValidString
CreatedBefore ValidTime
CreatedAfter ValidTime
IsActive ValidBool
Query ValidString
CreatedBefore ValidTime
CreatedAfter ValidTime
}
type GetCompany struct {
ID int64
Name string
AdminID int64
AdminFirstName string
AdminLastName string
AdminPhoneNumber string
WalletID int64
WalletBalance Currency
IsWalletActive bool
ID int64
Name string
AdminID int64
AdminFirstName string
AdminLastName string
AdminPhoneNumber string
WalletID int64
WalletBalance Currency
IsWalletActive bool
DeductedPercentage float32
IsActive bool
}
type CreateCompany struct {
Name string
AdminID int64
WalletID int64
Name string
AdminID int64
WalletID int64
DeductedPercentage float32
}
type UpdateCompany struct {
ID int64
Name *string
AdminID *int64
ID int64
Name ValidString
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

@ -101,6 +101,7 @@ type UpcomingEvent struct {
StartTime time.Time `json:"start_time"` // Converted from "time" field in UNIX format
Source string `json:"source"` // bet api provider (bet365, betfair)
Status EventStatus `json:"status"` //Match Status for event
Flagged bool `json:"flagged"` //Whether the event is flagged or not
}
type MatchResult struct {
EventID string
@ -127,4 +128,5 @@ type EventFilter struct {
Limit ValidInt64
Offset ValidInt64
MatchStatus ValidString // e.g., "upcoming", "in_play", "ended"
Flagged ValidBool
}

View File

@ -2,14 +2,51 @@ package domain
import "time"
type ReportedIssueType string
var (
ISSUE_TYPE_DEPOSIT ReportedIssueType = "deposit"
ISSUE_TYPE_WITHDRAWAL ReportedIssueType = "withdrawal"
ISSUE_TYPE_BET ReportedIssueType = "bet"
ISSUE_TYPE_CASHOUT ReportedIssueType = "cashout"
ISSUE_TYPE_ODDS ReportedIssueType = "odds"
ISSUE_TYPE_EVENTS ReportedIssueType = "events"
ISSUE_TYPE_BRANCH ReportedIssueType = "branch"
ISSUE_TYPE_USER ReportedIssueType = "branch"
ISSUE_TYPE_LOGIN ReportedIssueType = "login"
ISSUE_TYPE_REGISTER ReportedIssueType = "register"
ISSUE_TYPE_RESET_PASSWORD ReportedIssueType = "reset_password"
ISSUE_TYPE_WALLET ReportedIssueType = "wallet"
ISSUE_TYPE_VIRTUAL ReportedIssueType = "virtual games"
ISSUE_TYPE_OTHER ReportedIssueType = "other"
)
type ReportedIssueStatus string
var (
ISSUE_STATUS_PENDING ReportedIssueStatus = "pending"
ISSUE_STATUS_IN_PROGRESS ReportedIssueStatus = "in_progress"
ISSUE_STATUS_RESOLVED ReportedIssueStatus = "resolved"
ISSUE_STATUS_REJECTED ReportedIssueStatus = "rejected"
)
type ReportedIssue struct {
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
UserID int64 `json:"user_id"`
UserRole Role `json:"user_role"`
Subject string `json:"subject"`
Description string `json:"description"`
IssueType string `json:"issue_type"`
Status string `json:"status"`
IssueType ReportedIssueType `json:"issue_type"`
Status ReportedIssueStatus `json:"status"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ReportedIssueReq struct {
ID int64 `json:"id"`
Subject string `json:"subject"`
Description string `json:"description"`
IssueType ReportedIssueType `json:"issue_type"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}

View File

@ -1,6 +1,8 @@
package domain
import "time"
import (
"time"
)
type Setting struct {
Key string
@ -15,8 +17,37 @@ type SettingRes struct {
}
type SettingList struct {
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
BetAmountLimit Currency `json:"bet_amount_limit"`
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
TotalWinningLimit Currency `json:"total_winning_limit"`
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
BetAmountLimit Currency `json:"bet_amount_limit"`
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
TotalWinningLimit Currency `json:"total_winning_limit"`
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
}
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
}
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" example:"true"`
}

View File

@ -21,27 +21,14 @@ var (
func convertDBBet(bet dbgen.Bet) domain.Bet {
return domain.Bet{
ID: bet.ID,
Amount: domain.Currency(bet.Amount),
TotalOdds: bet.TotalOdds,
Status: domain.OutcomeStatus(bet.Status),
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: domain.ValidInt64{
Value: bet.BranchID.Int64,
Valid: bet.BranchID.Valid,
},
CompanyID: domain.ValidInt64{
Value: bet.CompanyID.Int64,
Valid: bet.CompanyID.Valid,
},
UserID: domain.ValidInt64{
Value: bet.UserID.Int64,
Valid: bet.UserID.Valid,
},
ID: bet.ID,
Amount: domain.Currency(bet.Amount),
TotalOdds: bet.TotalOdds,
Status: domain.OutcomeStatus(bet.Status),
UserID: bet.UserID,
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
CashoutID: bet.CashoutID,
FastCode: bet.FastCode,
CreatedAt: bet.CreatedAt.Time,
}
}
@ -78,21 +65,14 @@ func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
Amount: domain.Currency(bet.Amount),
TotalOdds: bet.TotalOdds,
Status: domain.OutcomeStatus(bet.Status),
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: domain.ValidInt64{
Value: bet.BranchID.Int64,
Valid: bet.BranchID.Valid,
},
UserID: domain.ValidInt64{
Value: bet.UserID.Int64,
Valid: bet.UserID.Valid,
},
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
CashoutID: bet.CashoutID,
Outcomes: outcomes,
CreatedAt: bet.CreatedAt.Time,
FullName: bet.FullName.(string),
PhoneNumber: bet.PhoneNumber.String,
UserID: bet.UserID,
IsShopBet: bet.IsShopBet,
CashedOut: bet.CashedOut,
Outcomes: outcomes,
FastCode: bet.FastCode,
CreatedAt: bet.CreatedAt.Time,
}
}
@ -119,22 +99,11 @@ func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateB
func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams {
return dbgen.CreateBetParams{
Amount: int64(bet.Amount),
TotalOdds: bet.TotalOdds,
Status: int32(bet.Status),
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: pgtype.Int8{
Int64: bet.BranchID.Value,
Valid: bet.BranchID.Valid,
},
UserID: pgtype.Int8{
Int64: bet.UserID.Value,
Valid: bet.UserID.Valid,
},
Amount: int64(bet.Amount),
TotalOdds: bet.TotalOdds,
Status: int32(bet.Status),
UserID: bet.UserID,
IsShopBet: bet.IsShopBet,
CashoutID: bet.CashoutID,
OutcomesHash: bet.OutcomesHash,
FastCode: bet.FastCode,
}
@ -184,33 +153,16 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
return convertDBBetWithOutcomes(bet), nil
}
func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
bet, err := s.queries.GetBetByCashoutID(ctx, id)
if err != nil {
domain.MongoDBLogger.Error("failed to get bet by cashout ID",
zap.String("cashout_id", id),
zap.Error(err),
)
return domain.GetBet{}, err
}
return convertDBBetWithOutcomes(bet), nil
}
func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) {
bets, err := s.queries.GetAllBets(ctx, dbgen.GetAllBetsParams{
BranchID: pgtype.Int8{
Int64: filter.BranchID.Value,
Valid: filter.BranchID.Valid,
},
CompanyID: pgtype.Int8{
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
UserID: pgtype.Int8{
Int64: filter.UserID.Value,
Valid: filter.UserID.Valid,
},
CashedOut: pgtype.Bool{
Bool: filter.CashedOut.Value,
Valid: filter.CashedOut.Valid,
},
IsShopBet: pgtype.Bool{
Bool: filter.IsShopBet.Value,
Valid: filter.IsShopBet.Valid,
@ -244,32 +196,8 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
return result, nil
}
func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) {
bets, err := s.queries.GetBetByBranchID(ctx, pgtype.Int8{
Int64: BranchID,
Valid: true,
})
if err != nil {
domain.MongoDBLogger.Error("failed to get bets by branch ID",
zap.Int64("branch_id", BranchID),
zap.Error(err),
)
return nil, err
}
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
for _, bet := range bets {
result = append(result, convertDBBetWithOutcomes(bet))
}
return result, nil
}
func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) {
bets, err := s.queries.GetBetByUserID(ctx, pgtype.Int8{
Int64: UserID,
Valid: true,
})
bets, err := s.queries.GetBetByUserID(ctx, UserID)
if err != nil {
return nil, err
@ -311,7 +239,7 @@ func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error)
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
UserID: pgtype.Int8{Int64: UserID, Valid: true},
UserID: UserID,
OutcomesHash: outcomesHash,
})
@ -496,23 +424,23 @@ func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
argPos := 1
// Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
if len(args) == 0 {
return " WHERE "
}
return " AND "
}(), argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
// if filter.CompanyID.Valid {
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
// args = append(args, filter.CompanyID.Value)
// argPos++
// }
// if filter.BranchID.Valid {
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
// if len(args) == 0 {
// return " WHERE "
// }
// return " AND "
// }(), argPos)
// args = append(args, filter.BranchID.Value)
// argPos++
// }
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 {
return " WHERE "
}
@ -586,21 +514,21 @@ func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]
argPos := 1
// Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
if len(args) == 0 {
return " WHERE "
}
return " AND "
}(), argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
// if filter.CompanyID.Valid {
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
// args = append(args, filter.CompanyID.Value)
// argPos++
// }
// if filter.BranchID.Valid {
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
// if len(args) == 0 {
// return " WHERE "
// }
// return " AND "
// }(), argPos)
// args = append(args, filter.BranchID.Value)
// argPos++
// }
if filter.UserID.Valid {
query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
if len(args) == 0 {
@ -700,16 +628,16 @@ func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilt
argPos := 1
// 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.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 {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
@ -790,16 +718,6 @@ func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFil
argPos := 1
// 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 {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
@ -876,21 +794,21 @@ func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter
argPos := 1
// Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
if len(args) == 0 {
return " WHERE "
}
return " AND "
}(), argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
// if filter.CompanyID.Valid {
// query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
// args = append(args, filter.CompanyID.Value)
// argPos++
// }
// if filter.BranchID.Valid {
// query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
// if len(args) == 0 {
// return " WHERE "
// }
// return " AND "
// }(), argPos)
// args = append(args, filter.BranchID.Value)
// argPos++
// }
if filter.UserID.Valid {
query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
if len(args) == 0 {
@ -986,16 +904,16 @@ func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.Report
argPos := 1
// Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
// if filter.CompanyID.Valid {
// query += fmt.Sprintf(" AND company_id = $%d", argPos)
// args = append(args, filter.CompanyID.Value)
// argPos++
// }
// if filter.BranchID.Valid {
// query += fmt.Sprintf(" AND branch_id = $%d", argPos)
// args = append(args, filter.BranchID.Value)
// argPos++
// }
if filter.UserID.Valid {
query += fmt.Sprintf(" AND user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
@ -1081,16 +999,16 @@ func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFi
argPos := 1
// Add filters if provided
if filter.CompanyID.Valid {
query += fmt.Sprintf(" AND company_id = $%d", argPos)
args = append(args, filter.CompanyID.Value)
argPos++
}
if filter.BranchID.Valid {
query += fmt.Sprintf(" AND branch_id = $%d", argPos)
args = append(args, filter.BranchID.Value)
argPos++
}
// if filter.CompanyID.Valid {
// query += fmt.Sprintf(" AND company_id = $%d", argPos)
// args = append(args, filter.CompanyID.Value)
// argPos++
// }
// if filter.BranchID.Valid {
// query += fmt.Sprintf(" AND branch_id = $%d", argPos)
// args = append(args, filter.BranchID.Value)
// argPos++
// }
if filter.StartTime.Valid {
query += fmt.Sprintf(" AND created_at >= $%d", argPos)
args = append(args, filter.StartTime.Value)
@ -1165,16 +1083,16 @@ func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFil
args := []interface{}{}
argPos := 1
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.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 {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
@ -1250,16 +1168,16 @@ func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter)
args := []interface{}{}
argPos := 1
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.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 {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)
@ -1326,16 +1244,16 @@ func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.Repo
args := []interface{}{}
argPos := 1
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.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 {
query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
args = append(args, filter.UserID.Value)

View File

@ -3,95 +3,28 @@ package repository
import (
"context"
"fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"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) {
dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company))
dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company))
if err != nil {
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) {
dbCompanies, err := s.queries.GetAllCompanies(ctx, 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,
},
})
dbCompanies, err := s.queries.GetAllCompanies(ctx, domain.ConvertGetAllCompaniesParams(filter))
if err != nil {
return nil, err
}
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompanyDetails(dbCompany))
companies = append(companies, domain.ConvertDBCompanyDetails(dbCompany))
}
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))
for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompanyDetails(dbCompany))
companies = append(companies, domain.ConvertDBCompanyDetails(dbCompany))
}
return companies, nil
}
@ -120,17 +53,17 @@ func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany
if err != nil {
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) {
dbCompany, err := s.queries.UpdateCompany(ctx, convertUpdateCompany(company))
dbCompany, err := s.queries.UpdateCompany(ctx, domain.ConvertUpdateCompany(company))
if err != nil {
return domain.Company{}, err
}
return convertDBCompany(dbCompany), nil
return domain.ConvertDBCompany(dbCompany), nil
}
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(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
}
}
return upcomingEvents, nil
@ -121,6 +122,7 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
}
}
return upcomingEvents, nil
@ -157,6 +159,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
},
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
},
})
if err != nil {
@ -180,6 +186,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
}
}
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
@ -203,6 +210,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
},
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
},
})
if err != nil {
return nil, 0, err
@ -233,6 +244,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String,
Status: domain.EventStatus(event.Status.String),
Flagged: event.Flagged,
}, nil
}
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
@ -268,6 +280,13 @@ func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status do
}
func (s *Store) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{
ID: eventID,
Flagged: flagged,
})
}
func (s *Store) DeleteEvent(ctx context.Context, eventID string) error {
err := s.queries.DeleteEvent(ctx, eventID)
if err != nil {

View File

@ -9,9 +9,9 @@ import (
type ReportedIssueRepository interface {
CreateReportedIssue(ctx context.Context, arg dbgen.CreateReportedIssueParams) (dbgen.ReportedIssue, error)
ListReportedIssues(ctx context.Context, limit, offset int32) ([]dbgen.ReportedIssue, error)
ListReportedIssuesByCustomer(ctx context.Context, customerID int64, limit, offset int32) ([]dbgen.ReportedIssue, error)
ListReportedIssuesByUser(ctx context.Context, userID int64, limit, offset int32) ([]dbgen.ReportedIssue, error)
CountReportedIssues(ctx context.Context) (int64, error)
CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error)
CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error)
UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error
DeleteReportedIssue(ctx context.Context, id int64) error
}
@ -36,21 +36,21 @@ func (s *ReportedIssueRepo) ListReportedIssues(ctx context.Context, limit, offse
return s.store.queries.ListReportedIssues(ctx, params)
}
func (s *ReportedIssueRepo) ListReportedIssuesByCustomer(ctx context.Context, customerID int64, limit, offset int32) ([]dbgen.ReportedIssue, error) {
params := dbgen.ListReportedIssuesByCustomerParams{
CustomerID: customerID,
Limit: limit,
Offset: offset,
func (s *ReportedIssueRepo) ListReportedIssuesByUser(ctx context.Context, userID int64, limit, offset int32) ([]dbgen.ReportedIssue, error) {
params := dbgen.ListReportedIssuesByUserParams{
UserID: userID,
Limit: limit,
Offset: offset,
}
return s.store.queries.ListReportedIssuesByCustomer(ctx, params)
return s.store.queries.ListReportedIssuesByUser(ctx, params)
}
func (s *ReportedIssueRepo) CountReportedIssues(ctx context.Context) (int64, error) {
return s.store.queries.CountReportedIssues(ctx)
}
func (s *ReportedIssueRepo) CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error) {
return s.store.queries.CountReportedIssuesByCustomer(ctx, customerID)
func (s *ReportedIssueRepo) CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error) {
return s.store.queries.CountReportedIssuesByUser(ctx, userID)
}
func (s *ReportedIssueRepo) UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error {

View File

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

View File

@ -10,10 +10,8 @@ import (
type BetStore interface {
CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, 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)
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)
GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error)

View File

@ -20,8 +20,11 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"go.uber.org/zap"
)
@ -37,16 +40,23 @@ var (
ErrBranchIDRequired = errors.New("Branch ID required for this role")
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
ErrInvalidAmount = errors.New("Invalid amount")
ErrBetAmountTooHigh = errors.New("Cannot create a bet with an amount above limit")
ErrBetWinningTooHigh = errors.New("Total Winnings over set limit")
)
type Service struct {
betStore BetStore
eventSvc event.Service
prematchSvc odds.ServiceImpl
walletSvc wallet.Service
branchSvc branch.Service
logger *slog.Logger
mongoLogger *zap.Logger
betStore BetStore
eventSvc event.Service
prematchSvc odds.ServiceImpl
walletSvc wallet.Service
branchSvc branch.Service
companySvc company.Service
settingSvc settings.Service
notificationSvc *notificationservice.Service
logger *slog.Logger
mongoLogger *zap.Logger
}
func NewService(
@ -55,17 +65,23 @@ func NewService(
prematchSvc odds.ServiceImpl,
walletSvc wallet.Service,
branchSvc branch.Service,
companySvc company.Service,
settingSvc settings.Service,
notificationSvc *notificationservice.Service,
logger *slog.Logger,
mongoLogger *zap.Logger,
) *Service {
return &Service{
betStore: betStore,
eventSvc: eventSvc,
prematchSvc: prematchSvc,
walletSvc: walletSvc,
branchSvc: branchSvc,
logger: logger,
mongoLogger: mongoLogger,
betStore: betStore,
eventSvc: eventSvc,
prematchSvc: prematchSvc,
walletSvc: walletSvc,
branchSvc: branchSvc,
companySvc: companySvc,
settingSvc: settingSvc,
notificationSvc: notificationSvc,
logger: logger,
mongoLogger: mongoLogger,
}
}
@ -196,9 +212,19 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
return newOutcome, nil
}
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) {
if len(req.Outcomes) > 30 {
s.mongoLogger.Error("too many outcomes",
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role, companyID domain.ValidInt64) (domain.CreateBetRes, error) {
settingsList, err := s.settingSvc.GetSettingList(ctx)
if req.Amount < 1 {
return domain.CreateBetRes{}, ErrInvalidAmount
}
if req.Amount > settingsList.BetAmountLimit.Float32() {
return domain.CreateBetRes{}, ErrBetAmountTooHigh
}
if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) {
s.mongoLogger.Info("too many outcomes",
zap.Int("count", len(req.Outcomes)),
zap.Int64("user_id", userID),
)
@ -224,6 +250,16 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
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)
if err != nil {
s.mongoLogger.Error("failed to generate outcome hash",
@ -234,14 +270,6 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
}
count, err := s.GetBetCount(ctx, userID, outcomesHash)
if err != nil {
return domain.CreateBetRes{}, err
}
if count >= 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already pleaced twice")
}
cashoutID, err := s.GenerateCashoutID()
if err != nil {
s.mongoLogger.Error("failed to generate cashout ID",
zap.Int64("user_id", userID),
@ -249,6 +277,9 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
)
return domain.CreateBetRes{}, err
}
if count >= 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already placed twice")
}
fastCode := helpers.GenerateFastCode()
amount := req.Amount + (req.Amount * calculateAccumulator(len(outcomes)))
@ -257,15 +288,14 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
Amount: domain.ToCurrency(amount),
TotalOdds: totalOdds,
Status: domain.OUTCOME_STATUS_PENDING,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
CashoutID: cashoutID,
OutcomesHash: outcomesHash,
FastCode: fastCode,
UserID: userID,
}
switch role {
case domain.RoleCashier:
newBet.IsShopBet = true
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil {
s.mongoLogger.Error("failed to get branch by cashier",
@ -275,30 +305,20 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err
}
deductedAmount := req.Amount / 10
_, 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))
err = s.DeductBetFromBranchWallet(ctx, req.Amount, branch.WalletID, branch.CompanyID, userID)
if err != nil {
s.mongoLogger.Error("failed to deduct from wallet",
zap.Int64("wallet_id", branch.WalletID),
zap.Float32("amount", deductedAmount),
s.mongoLogger.Error("wallet deduction for bet failed",
zap.String("role", string(role)),
zap.Error(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:
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 {
s.mongoLogger.Error("branch ID required for admin/manager",
s.mongoLogger.Warn("branch ID required for admin/manager",
zap.Int64("user_id", userID),
)
return domain.CreateBetRes{}, ErrBranchIDRequired
@ -313,87 +333,42 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err
}
deductedAmount := req.Amount / 10
_, 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 {
s.mongoLogger.Error("wallet deduction failed",
zap.Int64("wallet_id", branch.WalletID),
zap.Float32("amount", deductedAmount),
if branch.BranchManagerID != userID {
s.mongoLogger.Warn("unauthorized branch for branch manager",
zap.Int64("branch_id", *req.BranchID),
zap.Error(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
if companyID.Valid && branch.CompanyID == companyID.Value {
s.mongoLogger.Warn("unauthorized company",
zap.Int64("branch_id", *req.BranchID),
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:
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 {
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.Error(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:
s.mongoLogger.Error("unknown role type",
zap.String("role", string(role)),
@ -428,6 +403,100 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
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) {
var newOdds []domain.CreateBetOutcome
@ -638,26 +707,38 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
// s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
var cashoutID string
cashoutID, err = s.GenerateCashoutID()
outcomesHash, err := generateOutcomeHash(randomOdds)
if err != nil {
s.mongoLogger.Error("Failed to generate cash out ID",
zap.Int64("userID", userID),
zap.Int64("branchID", branchID))
s.mongoLogger.Error("failed to generate outcome hash",
zap.Int64("user_id", userID),
zap.Error(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{
Amount: domain.ToCurrency(123.5),
TotalOdds: totalOdds,
Status: domain.OUTCOME_STATUS_PENDING,
FullName: "test" + randomNumber,
PhoneNumber: "0900000000",
CashoutID: cashoutID,
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
UserID: domain.ValidInt64{Valid: true, Value: userID},
Amount: domain.ToCurrency(123.5),
TotalOdds: totalOdds,
Status: domain.OUTCOME_STATUS_PENDING,
UserID: userID,
IsShopBet: true,
FastCode: fastCode,
}
bet, err := s.CreateBet(ctx, newBet)
@ -702,17 +783,10 @@ func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.Create
func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
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) {
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) {
return s.betStore.GetBetByUserID(ctx, UserID)
}
@ -1103,3 +1177,27 @@ func calculateAccumulator(outcomesCount int) float32 {
return 0
}
}
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

@ -16,4 +16,5 @@ type Service interface {
// GetAndStoreMatchResult(ctx context.Context, eventID string) error
UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error
UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error
UpdateFlagged(ctx context.Context, eventID string, flagged bool) error
}

View File

@ -369,6 +369,10 @@ func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status
return s.store.UpdateEventStatus(ctx, eventID, status)
}
func (s *service) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
return s.store.UpdateFlagged(ctx, eventID, flagged)
}
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID)

View File

@ -17,15 +17,19 @@ func New(repo repository.ReportedIssueRepository) *Service {
return &Service{repo: repo}
}
func (s *Service) CreateReportedIssue(ctx context.Context, issue domain.ReportedIssue) (domain.ReportedIssue, error) {
func (s *Service) CreateReportedIssue(ctx context.Context, issue domain.ReportedIssueReq, userID int64, role domain.Role) (domain.ReportedIssue, error) {
// metadata, err := json.Marshal(issue.Metadata)
// if err != nil {
// return domain.ReportedIssue{}, err
// }
params := dbgen.CreateReportedIssueParams{
// Map fields from domain.ReportedIssue to dbgen.CreateReportedIssueParams here.
// Example:
// Title: issue.Title,
// Description: issue.Description,
// CustomerID: issue.CustomerID,
// Status: issue.Status,
// Add other fields as necessary.
UserID: userID,
UserRole: string(role),
Subject: issue.Subject,
Description: issue.Description,
IssueType: string(issue.IssueType),
// Metadata: metadata,
}
dbIssue, err := s.repo.CreateReportedIssue(ctx, params)
if err != nil {
@ -36,17 +40,20 @@ func (s *Service) CreateReportedIssue(ctx context.Context, issue domain.Reported
ID: dbIssue.ID,
Subject: dbIssue.Subject,
Description: dbIssue.Description,
CustomerID: dbIssue.CustomerID,
Status: dbIssue.Status,
UserID: dbIssue.UserID,
UserRole: domain.Role(dbIssue.UserRole),
Status: domain.ReportedIssueStatus(dbIssue.Status),
IssueType: domain.ReportedIssueType(dbIssue.IssueType),
CreatedAt: dbIssue.CreatedAt.Time,
UpdatedAt: dbIssue.UpdatedAt.Time,
// Add other fields as necessary
}
return reportedIssue, nil
}
func (s *Service) GetIssuesForCustomer(ctx context.Context, customerID int64, limit, offset int) ([]domain.ReportedIssue, error) {
dbIssues, err := s.repo.ListReportedIssuesByCustomer(ctx, customerID, int32(limit), int32(offset))
func (s *Service) GetIssuesForUser(ctx context.Context, userID int64, limit, offset int) ([]domain.ReportedIssue, error) {
dbIssues, err := s.repo.ListReportedIssuesByUser(ctx, userID, int32(limit), int32(offset))
if err != nil {
return nil, err
}
@ -56,8 +63,10 @@ func (s *Service) GetIssuesForCustomer(ctx context.Context, customerID int64, li
ID: dbIssue.ID,
Subject: dbIssue.Subject,
Description: dbIssue.Description,
CustomerID: dbIssue.CustomerID,
Status: dbIssue.Status,
UserID: dbIssue.UserID,
UserRole: domain.Role(dbIssue.UserRole),
Status: domain.ReportedIssueStatus(dbIssue.Status),
IssueType: domain.ReportedIssueType(dbIssue.IssueType),
CreatedAt: dbIssue.CreatedAt.Time,
UpdatedAt: dbIssue.UpdatedAt.Time,
// Add other fields as necessary

View File

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

View File

@ -25,8 +25,8 @@ var (
ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit")
ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached")
ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
ErrInvalidAmount = errors.New("Invalid amount")
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
)
type Service struct {
@ -34,7 +34,7 @@ type Service struct {
eventSvc event.Service
prematchSvc odds.ServiceImpl
mongoLogger *zap.Logger
settingSvc settings.Service
settingSvc settings.Service
notificationSvc *notificationservice.Service
}
@ -51,7 +51,7 @@ func NewService(
eventSvc: eventSvc,
prematchSvc: prematchSvc,
mongoLogger: mongoLogger,
settingSvc: settingSvc,
settingSvc: settingSvc,
notificationSvc: notificationSvc,
}
}
@ -167,6 +167,10 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
}
if req.Amount < 1 {
return domain.Ticket{}, 0, ErrInvalidAmount
}
// Check to see if the amount is above a set limit
if req.Amount > settingsList.BetAmountLimit.Float32() {
return domain.Ticket{}, 0, ErrTicketAmountTooHigh
@ -183,7 +187,6 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
// Check to see how many tickets a single anonymous user has created
if count > settingsList.DailyTicketPerIP {
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
}
@ -207,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
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("amount", req.Amount),
zap.Float32("limit", settingsList.TotalWinningLimit.Float32()))
@ -276,3 +279,24 @@ func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
func (s *Service) DeleteOldTickets(ctx context.Context) error {
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{
Outcomes: req.Outcomes,
Amount: req.Amount,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: branchID,
}, userID, role)
Outcomes: req.Outcomes,
Amount: req.Amount,
}, userID, role, userCompanyID)
if err != nil {
return domain.ShopBet{}, err

View File

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

View File

@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"strconv"
"time"
@ -31,28 +32,32 @@ type CreateAdminReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /admin [post]
// @Router /api/v1/admin [post]
func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
var companyID domain.ValidInt64
var req CreateAdminReq
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.Error(err),
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)
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",
zap.Int64("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs),
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 {
@ -69,7 +74,7 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
zap.Error(err),
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{
Value: *req.CompanyID,
@ -95,13 +100,16 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
zap.Error(err),
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 {
_, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
ID: *req.CompanyID,
AdminID: &newUser.ID,
ID: *req.CompanyID,
AdminID: domain.ValidInt64{
Value: newUser.ID,
Valid: true,
},
})
if err != nil {
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.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 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /admin [get]
// @Router /api/v1/admin [get]
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
searchQuery := c.Query("query")
@ -166,8 +174,8 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
@ -180,8 +188,8 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
@ -209,12 +217,16 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
valErrs, ok := h.validator.Validate(c, filter)
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.Any("validation_errors", valErrs),
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)
@ -225,7 +237,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
zap.Error(err),
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))
@ -241,7 +253,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
zap.Error(err),
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 401 {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 {
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
@ -294,7 +306,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err),
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)
@ -305,7 +317,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err),
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)
@ -316,7 +328,7 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
zap.Error(err),
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 {
lastLogin = &user.CreatedAt
@ -365,7 +377,7 @@ type updateAdminReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
var req updateAdminReq
if err := c.BodyParser(&req); err != nil {
@ -374,29 +386,33 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err),
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)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("UpdateAdmin failed - validation errors",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs),
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")
AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64)
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.String("admin_id_param", AdminIDStr),
zap.Error(err),
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
@ -430,13 +446,16 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err),
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 {
_, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
ID: *req.CompanyID,
AdminID: &AdminID,
ID: *req.CompanyID,
AdminID: domain.ValidInt64{
Value: AdminID,
Valid: true,
},
})
if err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed to update company",
@ -445,7 +464,7 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
zap.Error(err),
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 (
"errors"
"fmt"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
@ -36,41 +37,47 @@ type loginCustomerRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
var req loginCustomerReq
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.Error(err),
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 {
h.mongoLoggerSvc.Error("LoginCustomer validation failed",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("request", req),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid Request")
if valErrs, ok := h.validator.Validate(c, req); !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
if err != nil {
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 {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
case errors.Is(err, authentication.ErrUserSuspended):
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
default:
h.mongoLoggerSvc.Error("Login failed",
@ -125,7 +132,7 @@ type refreshToken struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
type loginCustomerRes struct {
@ -136,35 +143,47 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
var req refreshToken
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.Error(err),
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 {
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.Any("validation_errors", valErrs),
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)
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 {
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")
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")
default:
h.mongoLoggerSvc.Error("Refresh token failed",
@ -184,7 +203,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
zap.Error(err),
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)
@ -195,7 +214,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
zap.Error(err),
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{
@ -204,7 +223,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
Role: string(user.Role),
}
h.mongoLoggerSvc.Info("Refresh token successful",
h.mongoLoggerSvc.Info("Token Refreshed Successfully",
zap.Int("status_code", fiber.StatusOK),
zap.Int64("user_id", user.ID),
zap.String("role", string(user.Role)),
@ -229,39 +248,53 @@ type logoutReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
var req logoutReq
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.Error(err),
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 {
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.Any("validation_errors", valErrs),
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)
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 {
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")
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")
default:
h.mongoLoggerSvc.Error("Logout failed",
@ -269,7 +302,7 @@ func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
zap.Error(err),
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
// @Failure 400 {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 {
userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
var req domain.CreateBetReq
if err := c.BodyParser(&req); err != nil {
@ -35,16 +36,18 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
zap.Error(err),
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 {
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.String("role", string(role)),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error())
}
h.mongoLoggerSvc.Info("Bet created successfully",
@ -66,10 +69,11 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes
// @Failure 400 {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 {
userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
var req struct {
FastCode string `json:"fast_code"`
@ -82,27 +86,29 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
zap.Error(err),
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)
if err != nil {
h.mongoLoggerSvc.Error("falied to get bet with fast code",
zap.Int("status_code", fiber.StatusInternalServerError),
h.mongoLoggerSvc.Info("failed to get bet with fast code",
zap.String("fast_code", req.FastCode),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
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)
if err != nil {
h.mongoLoggerSvc.Error("falied to get BetOutcomes by BetID",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID",
zap.Int64("bet_id", bet.ID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "falied to get BetOutcomes by BetID")
return fiber.NewError(fiber.StatusBadRequest, "failed to get BetOutcomes by BetID:"+err.Error())
}
bet_outcomes := []domain.CreateBetOutcomeReq{}
@ -142,30 +148,37 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
PhoneNumber: user.PhoneNumber,
}
res, err := h.CreateBetInternal(c, newReq, userID, role)
res, err := h.CreateBetInternal(c, newReq, userID, role, companyID)
if err != nil {
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.String("role", string(role)),
zap.Any("newReq", newReq),
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)
// TODO: amount added for fast code owner can be fetched from settings in db
amount := domain.Currency(100)
// amount added for fast code owner can be fetched from settings in db
settingList, err := h.settingSvc.GetSettingList(c.Context())
amount := settingList.AmountForBetReferral
_, 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()))
if err != nil {
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.Float32("amount", amount.Float32()),
zap.Int64("static wallet_id", wallet.StaticID),
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",
@ -176,7 +189,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
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)
if !ok {
h.mongoLoggerSvc.Error("CreateBet validation failed",
@ -187,19 +200,30 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
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 {
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",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("userID", userID),
zap.Int64("companyID", companyID.Value),
zap.String("role", string(role)),
zap.Error(err),
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")
}
@ -216,28 +240,28 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
// @Success 200 {object} domain.BetRes
// @Failure 400 {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 {
userID := c.Locals("user_id").(int64)
leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
if err != nil {
h.mongoLoggerSvc.Error("invalid league id",
h.mongoLoggerSvc.Info("invalid league id",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
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"))
if err != nil {
h.mongoLoggerSvc.Error("invalid sport id",
h.mongoLoggerSvc.Info("invalid sport id",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
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")
@ -256,12 +280,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
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.Error(err),
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{
Value: firstStartTimeParsed,
@ -273,12 +298,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
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.Error(err),
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{
Value: lastStartTimeParsed,
@ -288,38 +314,45 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
var req domain.RandomBetReq
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.Error(err),
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)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("RandomBet validation failed",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs),
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
for i := 0; i < int(req.NumberOfBets); i++ {
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
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.Error(err),
zap.Time("timestamp", time.Now()),
)
switch err {
case bet.ErrNoEventsAvailable:
return fiber.NewError(fiber.StatusBadRequest, "No events found")
}
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet:"+err.Error())
}
}
@ -341,23 +374,24 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Success 200 {array} domain.BetRes
// @Failure 400 {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 {
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64)
// companyID := c.Locals("company_id").(domain.ValidInt64)
// branchID := c.Locals("branch_id").(domain.ValidInt64)
var isShopBet domain.ValidBool
isShopBetQuery := c.Query("is_shop")
if isShopBetQuery != "" && role == domain.RoleSuperAdmin {
isShopBetParse, err := strconv.ParseBool(isShopBetQuery)
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.String("is_shop", isShopBetQuery),
zap.Error(err),
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{
Value: isShopBetParse,
@ -376,8 +410,13 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_before format",
zap.String("time", createdBeforeQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
@ -390,8 +429,13 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
@ -399,24 +443,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{
BranchID: branchID,
CompanyID: companyID,
IsShopBet: isShopBet,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
})
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.Error(err),
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))
@ -424,11 +463,6 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
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)
}
@ -442,12 +476,12 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes
// @Failure 400 {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 {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
h.mongoLoggerSvc.Error("Invalid bet ID",
h.mongoLoggerSvc.Info("Invalid bet ID",
zap.String("betID", betID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
@ -458,7 +492,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
bet, err := h.betSvc.GetBetByID(c.Context(), id)
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.Int("status_code", fiber.StatusNotFound),
zap.Error(err),
@ -469,47 +503,11 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
res := domain.ConvertBet(bet)
h.mongoLoggerSvc.Info("Bet retrieved successfully",
zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusOK),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
// 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()),
)
// h.mongoLoggerSvc.Info("Bet retrieved successfully",
// zap.Int64("betID", id),
// zap.Int("status_code", fiber.StatusOK),
// zap.Time("timestamp", time.Now()),
// )
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
@ -529,7 +527,7 @@ type UpdateCashOutReq struct {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
type UpdateCashOutReq struct {
CashedOut bool `json:"cashed_out" validate:"required" example:"true"`
@ -555,11 +553,15 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
zap.Error(err),
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 {
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)
@ -592,7 +594,7 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
@ -614,7 +616,7 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error {
zap.Error(err),
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",

View File

@ -1,8 +1,11 @@
package handlers
import (
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
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 {
h.logger.Error("failed to parse bonus multiplier", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.logger.Error("failed to parse bonus multiplier request", "error", err)
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
@ -21,29 +29,42 @@ func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
if err != nil {
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 {
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 {
h.logger.Error("failed to create bonus multiplier", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "failed to create bonus mulitplier", nil, nil)
h.mongoLoggerSvc.Error("failed to create bonus multiplier",
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 {
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
if err != nil {
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())
}
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 {
@ -54,14 +75,24 @@ func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error {
}
if err := c.BodyParser(&req); err != nil {
h.logger.Error("failed to parse bonus multiplier", "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())
}
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)
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/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type CreateCashierReq struct {
@ -32,7 +33,7 @@ type CreateCashierReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /cashiers [post]
// @Router /api/v1/cashiers [post]
func (h *Handler) CreateCashier(c *fiber.Ctx) error {
// Get user_id from middleware
@ -40,19 +41,33 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
var req CreateCashierReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("failed to parse CreateCashier 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())
}
valErrs, ok := h.validator.Validate(c, req)
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
// 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)
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{
FirstName: req.FirstName,
@ -70,14 +85,25 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
fmt.Print(req.Suspended)
newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true)
if err != nil {
h.logger.Error("CreateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil)
h.mongoLoggerSvc.Error("Failed to create cashier user",
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)
if err != nil {
h.logger.Error("CreateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil)
h.mongoLoggerSvc.Error("failed to create branch cashier",
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)
@ -111,17 +137,22 @@ type GetCashierRes struct {
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {object} response.APIResponse
// @Success 200 {array} GetCashierRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /cashiers [get]
// @Router /api/v1/cashiers [get]
func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64)
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")
searchString := domain.ValidString{
@ -134,8 +165,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_before format",
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{
Value: createdBeforeParsed,
@ -148,8 +184,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
@ -174,7 +215,16 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
valErrs, ok := h.validator.Validate(c, filter)
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{
@ -183,8 +233,12 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
CreatedAfter: createdAfter,
})
if err != nil {
h.logger.Error("GetAllCashiers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
h.mongoLoggerSvc.Error("failed to get all cashiers",
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))
@ -195,8 +249,13 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &cashier.CreatedAt
} else {
h.logger.Error("Failed to get user last login", "userID", cashier.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
h.mongoLoggerSvc.Error("Failed to get 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 401 {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 {
// branchId := int64(12) //c.Locals("branch_id").(int64)
// filter := user.Filter{
@ -256,22 +315,37 @@ func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
stringID := c.Params("id")
cashierID, err := strconv.ParseInt(stringID, 10, 64)
if err != nil {
h.logger.Error("failed to fetch user using UserID", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
h.mongoLoggerSvc.Info("failed to parse user_id",
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)
if err != nil {
h.logger.Error("Get User By ID failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
h.mongoLoggerSvc.Error("Get User By ID failed",
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)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "cashierID", user.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
h.mongoLoggerSvc.Error("Failed to get 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
}
@ -316,24 +390,42 @@ type updateCashierReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
cashierIdStr := c.Params("id")
cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64)
if err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
h.mongoLoggerSvc.Info("UpdateCashier invalid cashier ID",
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
if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
h.mongoLoggerSvc.Info("UpdateCashier 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:"+err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
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{
@ -353,8 +445,14 @@ func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
},
)
if err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cashier", nil, nil)
h.mongoLoggerSvc.Error("failed to update cashier",
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)

View File

@ -0,0 +1 @@
package handlers

View File

@ -1,92 +1,60 @@
package handlers
import (
"fmt"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"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
// @Summary Create a company
// @Description Creates a company
// @Tags company
// @Accept json
// @Produce json
// @Param createCompany body CreateCompanyReq true "Creates company"
// @Success 200 {object} CompanyRes
// @Param createCompany body domain.CreateCompanyReq true "Creates company"
// @Success 200 {object} domain.CompanyRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /company [post]
// @Router /api/v1/company [post]
func (h *Handler) CreateCompany(c *fiber.Ctx) error {
var req CreateCompanyReq
var req domain.CreateCompanyReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("CreateCompanyReq failed",
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)
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)
if err != nil {
h.logger.Error("Error fetching user", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get user", err, nil)
h.mongoLoggerSvc.Error("Error fetching user",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
// Create Company Wallet
@ -98,8 +66,13 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
})
if err != nil {
h.logger.Error("Create Company Wallet failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create company wallet", err, nil)
h.mongoLoggerSvc.Error("Create Company Wallet failed",
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{
@ -109,20 +82,29 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
})
if err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
h.mongoLoggerSvc.Error("CreateCompanyReq failed to create company",
zap.Int64("userID", user.ID),
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)
if err != nil {
h.logger.Error("CreateCompanyReq failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
h.mongoLoggerSvc.Error("Failed to update user company",
zap.Int64("userID", user.ID),
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)
}
@ -133,10 +115,10 @@ func (h *Handler) CreateCompany(c *fiber.Ctx) error {
// @Tags company
// @Accept json
// @Produce json
// @Success 200 {array} CompanyRes
// @Success 200 {array} domain.GetCompanyRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /company [get]
// @Router /api/v1/company [get]
func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
searchQuery := c.Query("query")
searchString := domain.ValidString{
@ -149,8 +131,13 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_before format",
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{
Value: createdBeforeParsed,
@ -163,8 +150,13 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
@ -178,14 +170,18 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
CreatedAfter: createdAfter,
})
if err != nil {
h.logger.Error("Failed to get companies", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil)
h.mongoLoggerSvc.Error("Failed to get companies",
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 {
result = append(result, convertGetCompany(company))
result = append(result, domain.ConvertGetCompany(company))
}
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
// @Produce json
// @Param id path int true "Company ID"
// @Success 200 {object} CompanyRes
// @Success 200 {object} domain.GetCompanyRes
// @Failure 400 {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 {
companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil {
h.logger.Error("Invalid company ID", "companyID", companyID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil)
h.mongoLoggerSvc.Info("Invalid company ID",
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)
if err != nil {
h.logger.Error("Failed to get company by ID", "companyID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil)
h.mongoLoggerSvc.Error("Failed to get company by ID",
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)
}
@ -230,24 +236,33 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
// @Tags company
// @Accept json
// @Produce json
// @Success 200 {object} CompanyRes
// @Success 200 {object} domain.GetCompanyRes
// @Failure 400 {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 {
companyID := c.Locals("company_id").(domain.ValidInt64)
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)
if err != nil {
h.logger.Error("Failed to get company by ID", "companyID", companyID.Value, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil)
h.mongoLoggerSvc.Error("Failed to get company by ID",
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)
}
@ -258,25 +273,34 @@ func (h *Handler) GetCompanyForAdmin(c *fiber.Ctx) error {
// @Tags company
// @Accept json
// @Produce json
// @Success 200 {array} CompanyRes
// @Success 200 {array} domain.CompanyRes
// @Failure 400 {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 {
searchQuery := c.Query("q")
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)
if err != nil {
h.logger.Error("Failed to get companies", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil)
h.mongoLoggerSvc.Info("Failed to get companies",
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 {
result = append(result, convertGetCompany(company))
result = append(result, domain.ConvertGetCompany(company))
}
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
// @Produce json
// @Param id path int true "Company ID"
// @Param updateCompany body UpdateCompanyReq true "Update Company"
// @Success 200 {object} CompanyRes
// @Param updateCompany body domain.UpdateCompanyReq true "Update Company"
// @Success 200 {object} domain.CompanyRes
// @Failure 400 {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 {
companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil {
h.logger.Error("Invalid company ID", "companyID", companyID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil)
h.mongoLoggerSvc.Info("Invalid company ID",
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 {
h.logger.Error("UpdateCompanyReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("UpdateCompanyReq failed",
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)
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{
ID: id,
Name: req.Name,
AdminID: req.AdminID,
})
company, err := h.companySvc.UpdateCompany(c.Context(), domain.ConvertUpdateCompanyReq(req))
if err != nil {
h.logger.Error("Failed to update company", "companyID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", err, nil)
h.mongoLoggerSvc.Error("Failed to update company",
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)
@ -341,21 +386,31 @@ func (h *Handler) UpdateCompany(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
companyID := c.Params("id")
id, err := strconv.ParseInt(companyID, 10, 64)
if err != nil {
h.logger.Error("Invalid Company ID", "companyID", companyID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Company ID", err, nil)
h.mongoLoggerSvc.Info("Invalid Company ID",
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)
if err != nil {
h.logger.Error("Failed to delete by ID", "Company ID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Company", err, nil)
h.mongoLoggerSvc.Info("Failed to delete by ID",
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)

View File

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

View File

@ -8,63 +8,9 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// GetALLPrematchOdds
// @Summary Retrieve all prematch odds
// @Description Retrieve all prematch odds from the database
// @Tags prematch
// @Accept json
// @Produce json
// @Success 200 {array} domain.Odd
// @Failure 500 {object} response.APIResponse
// @Router /odds [get]
func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context())
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all prematch odds", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All prematch odds retrieved successfully", odds, nil)
}
// GetRawOddsByMarketID
// @Summary Retrieve raw odds by Market ID
// @Description Retrieve raw odds records using a Market ID
// @Tags prematch
// @Accept json
// @Produce json
// @Param upcoming_id path string true "Upcoming ID"
// @Param market_id path string true "Market ID"
// @Success 200 {array} domain.RawOddsByMarketID
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /odds/upcoming/{upcoming_id}/market/{market_id} [get]
func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
marketID := c.Params("market_id")
upcomingID := c.Params("upcoming_id")
if marketID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing market_id", nil, nil)
}
if upcomingID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil)
}
rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID)
if err != nil {
// fmt.Printf("Failed to fetch raw odds: %v market_id:%v upcomingID:%v\n", err, marketID, upcomingID)
h.logger.Error("Failed to get raw odds by market ID", "marketID", marketID, "upcomingID", upcomingID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Raw odds retrieved successfully", rawOdds, nil)
}
// @Summary Retrieve all upcoming events
// @Description Retrieve all upcoming events from the database
// @Tags prematch
@ -79,7 +25,7 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
// @Param last_start_time query string false "End Time"
// @Success 200 {array} domain.UpcomingEvent
// @Failure 500 {object} response.APIResponse
// @Router /events [get]
// @Router /api/v1/events [get]
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
@ -97,8 +43,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if leagueIDQuery != "" {
leagueIDInt, err := strconv.Atoi(leagueIDQuery)
if err != nil {
h.logger.Error("invalid league id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
h.mongoLoggerSvc.Error("invalid league id",
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{
Value: int32(leagueIDInt),
@ -110,8 +61,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil {
h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
h.mongoLoggerSvc.Info("invalid sport id",
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{
Value: int32(sportIDint),
@ -123,8 +79,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid start_time format",
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{
Value: firstStartTimeParsed,
@ -137,8 +98,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid start_time format",
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{
Value: lastStartTimeParsed,
@ -151,6 +117,26 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Value: countryCodeQuery,
Valid: countryCodeQuery != "",
}
flaggedQuery := c.Query("flagged")
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(
c.Context(), domain.EventFilter{
SportID: sportID,
@ -160,12 +146,17 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Limit: limit,
Offset: offset,
CountryCode: countryCode,
Flagged: flagged,
})
// fmt.Printf("League ID: %v", leagueID)
if err != nil {
h.logger.Error("getting error", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil)
h.mongoLoggerSvc.Error("Failed to retrieve all upcoming events",
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))
@ -192,14 +183,18 @@ type TopLeague struct {
// @Produce json
// @Success 200 {array} TopLeague
// @Failure 500 {object} response.APIResponse
// @Router /top-leagues [get]
// @Router /api/v1/top-leagues [get]
func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
leagues, err := h.leagueSvc.GetFeaturedLeagues(c.Context())
if err != nil {
h.logger.Error("Error while fetching top leagues", "err", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get featured leagues", nil, nil)
h.mongoLoggerSvc.Error("Error while fetching top leagues",
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))
@ -212,8 +207,12 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
},
})
if err != nil {
fmt.Printf("Error while fetching events for top league %v \n", league.ID)
h.logger.Error("Error while fetching events for top league", "League ID", league.ID)
h.mongoLoggerSvc.Warn("Error while fetching events for top league",
zap.Int64("LeagueID", league.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
}
topLeague = append(topLeague, TopLeague{
LeagueID: league.ID,
@ -240,61 +239,34 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error {
// @Success 200 {object} domain.UpcomingEvent
// @Failure 400 {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 {
id := c.Params("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)
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)
}
// @Summary Retrieve prematch odds by upcoming ID (FI)
// @Description Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination
// @Tags prematch
// @Accept json
// @Produce json
// @Param upcoming_id path string true "Upcoming Event ID (FI)"
// @Param limit query int false "Number of results to return (default: 10)"
// @Param offset query int false "Number of results to skip (default: 0)"
// @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /odds/upcoming/{upcoming_id} [get]
func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error {
upcomingID := c.Params("upcoming_id")
if upcomingID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil)
}
limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10
if err != nil || limit <= 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid limit value", nil, nil)
}
offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0
if err != nil || offset < 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil)
}
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
}
type UpdateEventStatusReq struct {
}
@ -308,13 +280,78 @@ type UpdateEventStatusReq struct {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
eventID := c.Params("id")
err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED)
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)
}
type UpdateEventFlaggedReq struct {
Flagged bool `json:"flagged" example:"true"`
}
// UpdateEventFlagged godoc
// @Summary update the event flagged
// @Description Update the event flagged
// @Tags event
// @Accept json
// @Produce json
// @Param id path int true "Event ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/events/{id}/flag [put]
func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
eventID := c.Params("id")
var req UpdateEventFlaggedReq
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())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
var errMsg string
for field, msg := range valErrs {
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)
}
err := h.eventSvc.UpdateFlagged(c.Context(), eventID, req.Flagged)
if err != nil {
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)

View File

@ -2,9 +2,11 @@ package handlers
import (
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// CreateIssue godoc
@ -19,42 +21,70 @@ import (
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues [post]
func (h *Handler) CreateIssue(c *fiber.Ctx) error {
var req domain.ReportedIssue
role := c.Locals("role").(domain.Role)
userID := c.Locals("user_id").(int64)
var req domain.ReportedIssueReq
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)
created, err := h.issueReportingSvc.CreateReportedIssue(c.Context(), req, userID, role)
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)
}
// GetCustomerIssues godoc
// @Summary Get reported issues by a customer
// @Description Returns all issues reported by a specific customer
// GetUserIssues godoc
// @Summary Get reported issues by a user
// @Description Returns all issues reported by a specific user
// @Tags Issues
// @Produce json
// @Param customer_id path int true "Customer ID"
// @Param user_id path int true "User ID"
// @Param limit query int false "Limit"
// @Param offset query int false "Offset"
// @Success 200 {array} domain.ReportedIssue
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues/customer/{customer_id} [get]
func (h *Handler) GetCustomerIssues(c *fiber.Ctx) error {
customerID, err := strconv.ParseInt(c.Params("customer_id"), 10, 64)
// @Router /api/v1/issues/user/{user_id} [get]
func (h *Handler) GetUserIssues(c *fiber.Ctx) error {
userID, err := strconv.ParseInt(c.Params("user_id"), 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid customer ID")
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")
}
limit, offset := getPaginationParams(c)
issues, err := h.issueReportingSvc.GetIssuesForCustomer(c.Context(), customerID, limit, offset)
issues, err := h.issueReportingSvc.GetIssuesForUser(c.Context(), userID, limit, offset)
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)
@ -75,7 +105,14 @@ func (h *Handler) GetAllIssues(c *fiber.Ctx) error {
issues, err := h.issueReportingSvc.GetAllIssues(c.Context(), limit, offset)
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)
@ -101,11 +138,24 @@ func (h *Handler) UpdateIssueStatus(c *fiber.Ctx) error {
Status string `json:"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 {
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 c.SendStatus(fiber.StatusNoContent)
@ -127,6 +177,12 @@ func (h *Handler) DeleteIssue(c *fiber.Ctx) error {
}
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())
}

View File

@ -3,10 +3,12 @@ package handlers
import (
"fmt"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// GetAllLeagues godoc
@ -18,7 +20,7 @@ import (
// @Success 200 {array} domain.League
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /leagues [get]
// @Router /api/v1/leagues [get]
func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
@ -49,8 +51,13 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil {
h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
h.mongoLoggerSvc.Info("invalid sport id",
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{
Value: int32(sportIDint),
@ -68,8 +75,12 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
if err != nil {
fmt.Printf("Error fetching league %v \n", err)
h.logger.Error("Failed to get leagues", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get leagues", err, nil)
h.mongoLoggerSvc.Error("Failed to get all leagues",
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)
}
@ -78,59 +89,132 @@ type SetLeagueActiveReq struct {
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 {
fmt.Printf("Set Active Leagues")
leagueIdStr := c.Params("id")
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)
if err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
}
var req SetLeagueActiveReq
if err := c.BodyParser(&req); err != nil {
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)
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 {
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)
}
func (h *Handler) SetLeagueAsFeatured(c *fiber.Ctx) error {
fmt.Printf("Set Active Leagues")
type SetLeagueAsFeatured struct {
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")
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)
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 {
h.logger.Error("SetLeagueReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to parse request", err, nil)
h.logger.Error("SetLeagueFeaturedReq failed", "error", err)
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)
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)

View File

@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"strconv"
"time"
@ -8,6 +9,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type CreateManagerReq struct {
@ -30,7 +32,7 @@ type CreateManagerReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /managers [post]
// @Router /api/v1/managers [post]
func (h *Handler) CreateManager(c *fiber.Ctx) error {
// Get user_id from middleware
@ -38,11 +40,26 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
var req CreateManagerReq
if err := c.BodyParser(&req); err != nil {
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)
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
@ -50,7 +67,11 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
if role == domain.RoleSuperAdmin {
if req.CompanyID == nil {
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{
Value: *req.CompanyID,
@ -71,8 +92,12 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
}
_, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil {
h.logger.Error("CreateManager failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create manager", nil, nil)
h.mongoLoggerSvc.Error("CreateManager failed to create manager",
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)
@ -106,14 +131,19 @@ type ManagersRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /managers [get]
// @Router /api/v1/managers [get]
func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64)
// Checking to make sure that admin user has a company id in the token
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")
@ -127,8 +157,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_before format",
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{
Value: createdBeforeParsed,
@ -141,8 +176,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
@ -167,12 +207,28 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
}
valErrs, ok := h.validator.Validate(c, filter)
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)
if err != nil {
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))
@ -182,8 +238,13 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &manager.CreatedAt
} else {
h.logger.Error("Failed to get user last login", "userID", manager.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
h.mongoLoggerSvc.Error("Failed to get 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{
@ -218,7 +279,7 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
role := c.Locals("role").(domain.Role)
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
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 {
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")
@ -241,24 +313,47 @@ func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
user, err := h.userSvc.GetUserByID(c.Context(), userID)
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
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
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)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
h.mongoLoggerSvc.Error("Failed to get user last login",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
}
lastLogin = &user.CreatedAt
@ -301,13 +396,14 @@ type updateManagerReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
var req updateManagerReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
}

View File

@ -7,6 +7,8 @@ import (
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
@ -14,6 +16,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gorilla/websocket"
"github.com/valyala/fasthttp/fasthttpadaptor"
"go.uber.org/zap"
)
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 {
userID, ok := c.Locals("userID").(int64)
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")
}
// Convert *fiber.Ctx to *http.Request
req, err := adaptor.ConvertRequest(c, false)
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")
}
// Create a net.Conn hijacked from the fasthttp context
netConn, rw, err := hijackHTTP(c)
if err != nil {
h.logger.Error("Failed to hijack connection", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to hijack connection")
h.mongoLoggerSvc.Error("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
conn, err := ws.Upgrader.Upgrade(rw, req, 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()
return fiber.NewError(fiber.StatusInternalServerError, "WebSocket upgrade failed")
return fiber.NewError(fiber.StatusInternalServerError, "WebSocket upgrade failed:"+err.Error())
}
client := &ws.Client{
@ -88,9 +110,19 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
_, _, err := conn.ReadMessage()
if err != nil {
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 {
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
}
@ -106,20 +138,34 @@ func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
var req Request
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")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification")
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 ID in context")
}
fmt.Printf("Notification IDs: %v \n", req.NotificationIDs)
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)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status")
h.mongoLoggerSvc.Error("Failed to mark notifications as read",
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"})
@ -141,7 +187,11 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
var req Request
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")
}
@ -174,11 +224,20 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
}
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)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send notification")
h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to send single 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})
case domain.NotificationDeliverySchemeBulk:
@ -186,12 +245,16 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
Role: string(req.Reciever),
})
if err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients")
h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification",
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))
for _, user := range recipients {
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 {
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
}
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{
"message": "Bulk notification sent successfully",
"recipient_count": len(recipients),
@ -224,7 +297,11 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
})
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")
}
}
@ -236,25 +313,45 @@ func (h *Handler) GetNotifications(c *fiber.Ctx) error {
// Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr)
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")
}
offset, err := strconv.Atoi(offsetStr)
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")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] Invalid user ID in context",
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)
if err != nil {
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] 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{
@ -274,15 +371,24 @@ func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
h.mongoLoggerSvc.Error("NotificationSvc.GetNotifications] 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")
}
total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID)
if err != nil {
h.logger.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
h.mongoLoggerSvc.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count",
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{
@ -297,18 +403,33 @@ func (h *Handler) GetAllNotifications(c *fiber.Ctx) error {
// Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr)
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")
}
page, err := strconv.Atoi(pageStr)
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")
}
notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit))
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")
}

View File

@ -0,0 +1,146 @@
package handlers
import (
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// GetALLPrematchOdds
// @Summary Retrieve all prematch odds
// @Description Retrieve all prematch odds from the database
// @Tags prematch
// @Accept json
// @Produce json
// @Success 200 {array} domain.Odd
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds [get]
func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context())
if err != 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)
}
// GetRawOddsByMarketID
// @Summary Retrieve raw odds by Market ID
// @Description Retrieve raw odds records using a Market ID
// @Tags prematch
// @Accept json
// @Produce json
// @Param upcoming_id path string true "Upcoming ID"
// @Param market_id path string true "Market ID"
// @Success 200 {array} domain.RawOddsByMarketID
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds/upcoming/{upcoming_id}/market/{market_id} [get]
func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
marketID := c.Params("market_id")
if marketID == "" {
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 == "" {
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)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get raw odds by market ID",
zap.String("marketID", marketID),
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)
}
// @Summary Retrieve prematch odds by upcoming ID (FI)
// @Description Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination
// @Tags prematch
// @Accept json
// @Produce json
// @Param upcoming_id path string true "Upcoming Event ID (FI)"
// @Param limit query int false "Number of results to return (default: 10)"
// @Param offset query int false "Number of results to skip (default: 0)"
// @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds/upcoming/{upcoming_id} [get]
func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error {
upcomingID := c.Params("upcoming_id")
if upcomingID == "" {
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
if err != nil || limit <= 0 {
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
if err != nil || offset < 0 {
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)
if err != 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)
}

View File

@ -1,20 +1,33 @@
package handlers
import (
"fmt"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
h.mongoLoggerSvc.Info("Invalid user ID in context",
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 {
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")
}
@ -24,29 +37,54 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
var req domain.ReferralSettingsReq
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")
}
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())
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")
}
// only allow one referral setting for now
// for future it can be multiple and be able to choose from them
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")
}
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")
}
@ -63,23 +101,37 @@ func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/stats [get]
// @Router /api/v1/referral/stats [get]
func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
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 id")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
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")
}
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.PhoneNumber)
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")
}
@ -98,34 +150,60 @@ func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/settings [put]
// @Router /api/v1/referral/settings [put]
func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
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)
if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
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, err.Error())
}
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")
}
var settings domain.ReferralSettings
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")
}
settings.UpdatedBy = user.PhoneNumber
if err := h.referralSvc.UpdateReferralSettings(c.Context(), &settings); err != nil {
h.logger.Error("Failed to update referral settings", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update referral settings")
h.mongoLoggerSvc.Error("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)
@ -142,7 +220,7 @@ func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error {
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/settings [get]
// @Router /api/v1/referral/settings [get]
func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
// userID, ok := c.Locals("user_id").(int64)
// if !ok || userID == 0 {
@ -153,18 +231,34 @@ func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
user, err := h.userSvc.GetUserByID(c.Context(), userID)
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")
}
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")
}
settings, err := h.referralSvc.GetReferralSettings(c.Context())
if err != nil {
h.logger.Error("Failed to get referral settings", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral settings")
h.mongoLoggerSvc.Error("Failed to get 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)

View File

@ -2,10 +2,12 @@ package handlers
import (
"encoding/json"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type ResultRes struct {
@ -24,17 +26,26 @@ type ResultRes struct {
// @Success 200 {array} ResultRes
// @Failure 400 {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 {
eventID := c.Params("id")
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")
}
results, outcomes, err := h.resultSvc.GetResultsForEvent(c.Context(), eventID)
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")
}

View File

@ -2,13 +2,13 @@ package handlers
import (
"fmt"
"log/slog"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// CreateShopBet godoc
@ -21,7 +21,7 @@ import (
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
userID := c.Locals("user_id").(int64)
@ -30,20 +30,42 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
var req domain.ShopBetReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateBetReq failed to parse request", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("CreateBetReq failed to parse request",
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)
if !ok {
h.logger.Error("CreateBetReq failed v", "error", valErrs)
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)
}
shopBet, err := h.transactionSvc.CreateShopBet(c.Context(), userID, role, company_id, req)
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)
@ -60,7 +82,7 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
betIDstr := c.Params("id")
@ -68,15 +90,25 @@ func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
betID, err := strconv.ParseInt(betIDstr, 10, 64)
if err != nil {
h.logger.Error("CashoutReq failed bet id is invalid", "error", nil)
return response.WriteJSON(c, fiber.StatusBadRequest, "bet ID is invalid", nil, nil)
h.mongoLoggerSvc.Info("GetShopBetByBetID failed bet id is invalid",
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)
if err != nil {
h.logger.Error("CashoutReq failed invalid bet id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
h.mongoLoggerSvc.Error("GetShopBetByBetID failed invalid bet id",
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)
@ -94,7 +126,7 @@ func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
betIDStr := c.Params("id")
@ -102,8 +134,13 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
betID, err := strconv.ParseInt(betIDStr, 10, 64)
if err != nil {
h.logger.Error("CashoutReq invalid bet id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet id", err, nil)
h.mongoLoggerSvc.Info("CashoutReq invalid bet id",
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)
@ -112,20 +149,41 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
var req domain.CashoutReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CashoutReq failed to parse request", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("CashoutReq failed to parse request",
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)
if !ok {
h.logger.Error("CashoutReq failed v", "error", valErrs)
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("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)
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)
@ -142,7 +200,7 @@ func (h *Handler) CashoutBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
userID := c.Locals("user_id").(int64)
@ -151,27 +209,52 @@ func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error {
var req domain.CashoutReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CashoutReq failed to parse request", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("CashoutReq failed to parse request",
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)
if !ok {
h.logger.Error("CashoutReq failed v", "error", valErrs)
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("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)
if err != nil {
h.logger.Error("CashoutReq failed invalid cashout id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil)
h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id",
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)
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)
@ -188,21 +271,30 @@ func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
cashoutID := c.Params("id")
if cashoutID == "" {
h.logger.Error("CashoutReq failed cashout id is required", "error", nil)
return response.WriteJSON(c, fiber.StatusBadRequest, "cashout ID is required", nil, nil)
h.mongoLoggerSvc.Info("CashoutReq failed cashout id is required",
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)
if err != nil {
h.logger.Error("CashoutReq failed invalid cashout id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil)
h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id",
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)
@ -217,10 +309,10 @@ func (h *Handler) GetShopBetByCashoutID(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Param transferToWallet body domain.ShopDepositReq true "ShopDepositReq"
// @Success 200 {object} TransferWalletRes
// @Success 200 {object} domain.ShopDepositRes
// @Failure 400 {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 {
// Get sender ID from the cashier
@ -230,7 +322,12 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
var req domain.ShopDepositReq
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())
}
@ -240,14 +337,26 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
for field, msg := range valErrs {
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)
}
deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req)
if err != nil {
fmt.Printf("Shop Deposit Error %v \n", err)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
h.mongoLoggerSvc.Info("failed to create shop deposit",
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)
@ -265,7 +374,7 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
// @Success 200 {array} domain.ShopTransactionRes
// @Failure 400 {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 {
// Get user_id from middleware
// userID := c.Locals("user_id").(int64)
@ -284,8 +393,13 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid start_time format",
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{
Value: createdBeforeParsed,
@ -298,8 +412,13 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
h.mongoLoggerSvc.Info("invalid start_time format",
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{
Value: createdAfterParsed,
@ -317,8 +436,12 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
})
if err != nil {
h.logger.Error("Failed to get transactions", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil)
h.mongoLoggerSvc.Info("Failed to get transactions",
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))
@ -340,18 +463,28 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64)
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")
}
transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id)
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")
}
@ -369,28 +502,34 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
// @Success 200 {object} domain.ShopTransactionRes
// @Failure 400 {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 {
transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil {
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
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, err.Error())
}
transaction, err := h.transactionSvc.GetShopBetByShopTransactionID(c.Context(), id)
if err != nil {
h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction")
h.mongoLoggerSvc.Error("Failed to get 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, err.Error())
}
res := domain.ConvertShopBetDetail(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Shop bet retrieved successfully", res, nil)
}
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" example:"true"`
}
// UpdateTransactionVerified godoc
// @Summary Updates the verified field of a transaction
// @Description Updates the verified status of a transaction
@ -398,11 +537,11 @@ type UpdateTransactionVerifiedReq struct {
// @Accept json
// @Produce json
// @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
// @Failure 400 {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 {
transactionID := c.Params("id")
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)
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")
}
var req UpdateTransactionVerifiedReq
var req domain.UpdateTransactionVerifiedReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("Failed to parse UpdateTransactionVerified request",
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 {
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)
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")
}

View File

@ -1,12 +1,15 @@
package handlers
import (
"fmt"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// CreateTicket godoc
@ -19,29 +22,51 @@ import (
// @Success 200 {object} domain.CreateTicketRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [post]
// @Router /api/v1/ticket [post]
func (h *Handler) CreateTicket(c *fiber.Ctx) error {
var req domain.CreateTicketReq
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")
}
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())
if err != nil {
switch err {
case ticket.ErrEventHasBeenRemoved, ticket.ErrTicketHasExpired,
ticket.ErrRawOddInvalid, ticket.ErrTooManyOutcomesForTicket,
ticket.ErrTicketAmountTooHigh, ticket.ErrTicketLimitForSingleUser,
ticket.ErrTicketWinningTooHigh:
return fiber.NewError(fiber.StatusBadRequest, err.Error())
var statusCode int
if isTicketError := h.ticketSvc.CheckTicketError(err); isTicketError {
statusCode = fiber.StatusBadRequest
} 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{
FastCode: newTicket.ID,
@ -61,18 +86,28 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
// @Success 200 {object} domain.TicketRes
// @Failure 400 {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 {
ticketID := c.Params("id")
id, err := strconv.ParseInt(ticketID, 10, 64)
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")
}
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
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")
}
@ -94,12 +129,16 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
// @Success 200 {array} domain.TicketRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [get]
// @Router /api/v1/ticket [get]
func (h *Handler) GetAllTickets(c *fiber.Ctx) error {
tickets, err := h.ticketSvc.GetAllTickets(c.Context())
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")
}

View File

@ -127,7 +127,7 @@ type CreateRefillReq struct {
// @Success 200 {object} TransferWalletRes
// @Failure 400 {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 {
walletID := c.Params("id")
@ -135,14 +135,24 @@ func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error {
id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
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")
}
transfers, err := h.walletSvc.GetTransfersByWallet(c.Context(), int64(id))
if err != nil {
h.logger.Error("Failed to get transfers by wallet", "walletID", walletID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transfers", err, nil)
h.mongoLoggerSvc.Error("Failed to get transfers by wallet",
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
@ -164,7 +174,7 @@ func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error {
// @Success 200 {object} TransferWalletRes
// @Failure 400 {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 {
receiverIDString := c.Params("id")
@ -172,59 +182,100 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
receiverID, err := strconv.ParseInt(receiverIDString, 10, 64)
if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
h.mongoLoggerSvc.Info("Invalid wallet ID",
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
userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role)
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
//TODO: check to make sure that the cashiers aren't transferring TO branch wallet
switch role {
case domain.RoleCustomer:
h.logger.Error("Unauthorized access", "userID", userID, "role", role)
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
h.mongoLoggerSvc.Error("Unauthorized access",
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:
company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to fetch company",
Error: err.Error(),
})
// return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching company", err, nil)
h.mongoLoggerSvc.Error("Failed to fetch company",
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 = company.WalletID
h.logger.Error("Will", "userID", userID, "role", role)
// h.logger.Error("Will", "userID", userID, "role", role)
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:
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:
cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get branch", "user ID", userID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil)
h.mongoLoggerSvc.Error("Failed to get branch by cashier",
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
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
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateTransferReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Error("CreateTransferReq failed to parse 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")
}
valErrs, ok := h.validator.Validate(c, req)
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(),
@ -234,8 +285,13 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
)
if err != nil {
h.mongoLoggerSvc.Error("Failed to transfer money to wallet", zap.Error(err))
return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil)
h.mongoLoggerSvc.Error("Failed to transfer money to wallet",
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)
@ -243,7 +299,6 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil)
}
// RefillWallet godoc
// @Summary Refill 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
// @Failure 400 {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 {
receiverIDString := c.Params("id")
@ -263,21 +318,42 @@ func (h *Handler) RefillWallet(c *fiber.Ctx) error {
receiverID, err := strconv.ParseInt(receiverIDString, 10, 64)
if err != nil {
h.logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
h.mongoLoggerSvc.Error("Invalid wallet ID",
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
var req CreateRefillReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateRefillReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
h.mongoLoggerSvc.Info("CreateRefillReq failed to parse CreateRefillReq",
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)
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(
@ -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))
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)

View File

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

View File

@ -1,12 +1,14 @@
package handlers
import (
"fmt"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type WalletRes struct {
@ -94,18 +96,28 @@ type BranchWalletRes struct {
// @Success 200 {object} WalletRes
// @Failure 400 {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 {
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)
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")
}
wallet, err := h.walletSvc.GetWalletByID(c.Context(), id)
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")
}
@ -123,14 +135,18 @@ func (h *Handler) GetWalletByID(c *fiber.Ctx) error {
// @Success 200 {array} WalletRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /wallet [get]
// @Router /api/v1/wallet [get]
func (h *Handler) GetAllWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllWallets(c.Context())
if err != nil {
h.logger.Error("Failed to get wallets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil)
h.mongoLoggerSvc.Error("Failed to get wallets",
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))
@ -152,14 +168,18 @@ func (h *Handler) GetAllWallets(c *fiber.Ctx) error {
// @Success 200 {array} WalletRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /branchWallet [get]
// @Router /api/v1/branchWallet [get]
func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllBranchWallets(c.Context())
if err != nil {
h.logger.Error("Failed to get wallets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil)
h.mongoLoggerSvc.Error("Failed to get wallets",
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))
@ -191,14 +211,18 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
// @Success 200 {array} CustomerWalletRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /customerWallet [get]
// @Router /api/v1/customerWallet [get]
func (h *Handler) GetAllCustomerWallets(c *fiber.Ctx) error {
wallets, err := h.walletSvc.GetAllCustomerWallet(c.Context())
if err != nil {
h.logger.Error("Failed to get wallets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil)
h.mongoLoggerSvc.Error("Failed to get customer wallets",
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))
@ -225,29 +249,53 @@ type UpdateWalletActiveReq struct {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)
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")
}
var req UpdateWalletActiveReq
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")
}
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)
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")
}
@ -265,24 +313,37 @@ func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
// @Success 200 {object} CustomerWalletRes
// @Failure 400 {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 {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
h.mongoLoggerSvc.Info("Invalid user ID in context",
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)
if !ok {
h.logger.Error("Invalid role in context", "userID", userID)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid role")
h.mongoLoggerSvc.Error("Invalid role in context",
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 {
h.logger.Error("Unauthorized access", "userID", userID, "role", role)
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized access")
h.mongoLoggerSvc.Error("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)
@ -295,7 +356,12 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
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")
}
@ -315,40 +381,63 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /cashierWallet [get]
// @Router /api/v1/cashierWallet [get]
func (h *Handler) GetWalletForCashier(c *fiber.Ctx) error {
cashierID, ok := c.Locals("user_id").(int64)
if !ok || cashierID == 0 {
h.logger.Error("Invalid cashier ID in context")
return response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid cashier identification", nil, nil)
h.mongoLoggerSvc.Error("Invalid cashier ID in context",
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)
if !ok || role != domain.RoleCashier {
h.logger.Error("Unauthorized access", "cashierID", cashierID, "role", role)
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
h.mongoLoggerSvc.Error("Unauthorized access",
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)
if !ok || !branchID.Valid {
h.logger.Error("Invalid branch ID in context", "cashierID", cashierID)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", nil, nil)
h.mongoLoggerSvc.Info("Invalid branch ID in context",
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)
if err != nil {
h.logger.Error("Failed to get branch by ID", "branchID", branchID.Value, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch", err, nil)
h.mongoLoggerSvc.Error("Failed to get branch by ID",
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)
if err != nil {
h.logger.Error("Failed to get wallet for cashier", "cashierID", cashierID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil)
h.mongoLoggerSvc.Error("Failed to get wallet for cashier",
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{

View File

@ -4,22 +4,38 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
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")
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")
}
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")
}
@ -28,10 +44,20 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
claim, err := jwtutil.ParseJwt(accessToken, a.JwtConfig.JwtAccessKey)
if err != nil {
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")
}
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")
}
@ -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
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")
}
c.Locals("user_id", claim.UserId)
@ -57,8 +88,13 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
if claim.Role == domain.RoleCashier {
branch, err := a.branchSvc.GetBranchByCashier(c.Context(), claim.UserId)
if err != nil {
a.logger.Error("Failed to get branch id for bet", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to branch id for bet")
a.mongoLoggerSvc.Error("Failed to get branch id for cashier",
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{
Value: branch.ID,
@ -72,61 +108,116 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
}
func (a *App) SuperAdminOnly(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role)
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()
}
func (a *App) CompanyOnly(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role)
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()
}
func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role)
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()
}
func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role)
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()
}
func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
tokenStr := c.Query("token")
ip := c.IP()
userAgent := c.Get("User-Agent")
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")
}
claim, err := jwtutil.ParseJwt(tokenStr, a.JwtConfig.JwtAccessKey)
if err != nil {
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")
}
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")
}
userID := claim.UserId
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")
}
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()
}

View File

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

@ -47,6 +47,7 @@ postgres:
backup:
@mkdir -p backup
@docker exec -t fortunebet-backend-postgres-1 pg_dumpall -c -U root | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
restore:
@echo "Restoring latest backup..."
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
@ -55,6 +56,8 @@ restore:
restore_file:
@echo "Restoring latest backup..."
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:
docker logs fortunebet-backend-postgres-1
.PHONY: swagger