fixes while integrating

This commit is contained in:
Samuel Tariku 2025-08-31 13:30:26 +03:00
parent 42788e6f9f
commit 910d592bef
37 changed files with 2795 additions and 910 deletions

View File

@ -0,0 +1,343 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- 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,', 'Alamata,'),
('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;
-- Settings Initial Data
INSERT INTO global_settings (key, value)
VALUES ('sms_provider', 'afro_message'),
('max_number_of_outcomes', '30'),
('bet_amount_limit', '10000000'),
('daily_ticket_limit', '50'),
('total_winnings_limit', '1000000'),
('amount_for_bet_referral', '1000000'),
('cashback_amount_cap', '1000') ON CONFLICT (key) DO NOTHING;
-- Users
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended,
company_id
)
VALUES (
1,
'John',
'Doe',
'john.doe@example.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
1
),
(
2,
'Test',
'Admin',
'test.admin@gmail.com',
'0988554466',
crypt('password@123', gen_salt('bf'))::bytea,
'admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
1
),
(
3,
'Samuel',
'Tariku',
'cybersamt@gmail.com',
'0911111111',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
),
(
4,
'Kirubel',
'Kibru',
'kirubel.jkl679@gmail.com',
'0911554486',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
) ON CONFLICT (id) DO
UPDATE
SET first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name,
email = EXCLUDED.email,
phone_number = EXCLUDED.phone_number,
password = EXCLUDED.password,
role = EXCLUDED.role,
email_verified = EXCLUDED.email_verified,
phone_verified = EXCLUDED.phone_verified,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
suspended = EXCLUDED.suspended,
company_id = EXCLUDED.company_id;
-- Supported Operations
INSERT INTO supported_operations (id, name, description)
VALUES (1, 'SportBook', 'Sportbook operations'),
(2, 'Virtual', 'Virtual operations') ON CONFLICT (id) DO
UPDATE
SET name = EXCLUDED.name,
description = EXCLUDED.description;
-- Wallets
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
type,
currency,
is_active,
created_at,
updated_at
)
VALUES (
1,
10000,
TRUE,
TRUE,
TRUE,
1,
'regular_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
2,
5000,
FALSE,
TRUE,
TRUE,
1,
'static_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
3,
20000,
TRUE,
TRUE,
TRUE,
2,
'company_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
4,
15000,
TRUE,
TRUE,
TRUE,
2,
'branch_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) ON CONFLICT (id) DO
UPDATE
SET balance = EXCLUDED.balance,
is_withdraw = EXCLUDED.is_withdraw,
is_bettable = EXCLUDED.is_bettable,
is_transferable = EXCLUDED.is_transferable,
user_id = EXCLUDED.user_id,
type = EXCLUDED.type,
currency = EXCLUDED.currency,
is_active = EXCLUDED.is_active,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at;
-- Customer Wallets
INSERT INTO customer_wallets (
id,
customer_id,
regular_wallet_id,
static_wallet_id
)
VALUES (1, 1, 1, 2) ON CONFLICT (id) DO
UPDATE
SET customer_id = EXCLUDED.customer_id,
regular_wallet_id = EXCLUDED.regular_wallet_id,
static_wallet_id = EXCLUDED.static_wallet_id;
-- Company
INSERT INTO companies (
id,
name,
slug,
admin_id,
wallet_id,
deducted_percentage,
is_active,
created_at,
updated_at
)
VALUES (
1,
'FortuneBets',
'fortunebets',
2,
3,
0.10,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) ON CONFLICT (id) DO
UPDATE
SET name = EXCLUDED.name,
slug = EXCLUDED.slug,
admin_id = EXCLUDED.admin_id,
wallet_id = EXCLUDED.wallet_id,
deducted_percentage = EXCLUDED.deducted_percentage,
is_active = EXCLUDED.is_active,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at;
-- Branch
INSERT INTO branches (
id,
name,
location,
wallet_id,
branch_manager_id,
company_id,
is_self_owned,
profit_percent,
is_active,
created_at,
updated_at
)
VALUES (
1,
'Test Branch',
'addis_ababa',
4,
2,
1,
TRUE,
0.10,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) ON CONFLICT (id) DO
UPDATE
SET name = EXCLUDED.name,
location = EXCLUDED.location,
wallet_id = EXCLUDED.wallet_id,
branch_manager_id = EXCLUDED.branch_manager_id,
company_id = EXCLUDED.company_id,
is_self_owned = EXCLUDED.is_self_owned,
profit_percent = EXCLUDED.profit_percent,
is_active = EXCLUDED.is_active,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at;

148
db/data/002_veli_user.sql Normal file
View File

@ -0,0 +1,148 @@
-- Users
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended,
company_id
)
VALUES (
5,
'Test',
'Veli',
'test.veli@example.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
1
),
(
6,
'Kirubel',
'Kibru',
'modernkibru @gmail.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
1
) ON CONFLICT (id) DO
UPDATE
SET first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name,
email = EXCLUDED.email,
phone_number = EXCLUDED.phone_number,
password = EXCLUDED.password,
role = EXCLUDED.role,
email_verified = EXCLUDED.email_verified,
phone_verified = EXCLUDED.phone_verified,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
suspended = EXCLUDED.suspended,
company_id = EXCLUDED.company_id;
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
type,
currency,
is_active,
created_at,
updated_at
)
VALUES (
5,
10000,
TRUE,
TRUE,
TRUE,
1,
'regular_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
6,
5000,
FALSE,
TRUE,
TRUE,
1,
'static_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
7,
10000,
TRUE,
TRUE,
TRUE,
1,
'regular_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
8,
5000,
FALSE,
TRUE,
TRUE,
1,
'static_wallet',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) ON CONFLICT (id) DO
UPDATE
SET balance = EXCLUDED.balance,
is_withdraw = EXCLUDED.is_withdraw,
is_bettable = EXCLUDED.is_bettable,
is_transferable = EXCLUDED.is_transferable,
user_id = EXCLUDED.user_id,
type = EXCLUDED.type,
currency = EXCLUDED.currency,
is_active = EXCLUDED.is_active,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at;
-- Customer Wallets
INSERT INTO customer_wallets (
id,
customer_id,
regular_wallet_id,
static_wallet_id
)
VALUES (2, 5, 5, 6),
(3, 6, 7, 8) ON CONFLICT (id) DO
UPDATE
SET customer_id = EXCLUDED.customer_id,
regular_wallet_id = EXCLUDED.regular_wallet_id,
static_wallet_id = EXCLUDED.static_wallet_id;

View File

@ -1,222 +0,0 @@
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Users
INSERT INTO users (
id,
first_name,
last_name,
email,
phone_number,
password,
role,
email_verified,
phone_verified,
created_at,
updated_at,
suspended,
company_id
)
VALUES (
1,
'John',
'Doe',
'john.doe@example.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
),
(
2,
'Test',
'Admin',
'test.admin@gmail.com',
'0988554466',
crypt('password123', gen_salt('bf'))::bytea,
'admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
1
),
(
3,
'Samuel',
'Tariku',
'cybersamt@gmail.com',
'0911111111',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
),
(
4,
'Kirubel',
'Kibru',
'kirubel.jkl679@gmail.com',
'0911554486',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
),
(
5,
'Test',
'Veli',
'test.veli@example.com',
NULL,
crypt('password@123', gen_salt('bf'))::bytea,
'customer',
TRUE,
FALSE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
FALSE,
NULL
);
-- Supported Operations
INSERT INTO supported_operations (id, name, description)
VALUES (1, 'SportBook', 'Sportbook operations'),
(2, 'Virtual', 'Virtual operations');
-- Wallets
INSERT INTO wallets (
id,
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
type,
currency,
is_active,
created_at,
updated_at
)
VALUES (
1,
10000,
TRUE,
TRUE,
TRUE,
1,
'regular',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
2,
5000,
FALSE,
TRUE,
TRUE,
1,
'static',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
3,
20000,
TRUE,
TRUE,
TRUE,
2,
'company_main',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
4,
15000,
TRUE,
TRUE,
TRUE,
2,
'branch_main',
'ETB',
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
-- Customer Wallets
INSERT INTO customer_wallets (
id,
customer_id,
regular_wallet_id,
static_wallet_id
)
VALUES (1, 1, 1, 2);
-- Company
INSERT INTO companies (
id,
name,
slug,
admin_id,
wallet_id,
deducted_percentage,
is_active,
created_at,
updated_at
)
VALUES (
1,
'FortuneBets',
'fortunebets',
2,
3,
0.10,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
-- Branch
INSERT INTO branches (
id,
name,
location,
wallet_id,
branch_manager_id,
company_id,
is_self_owned,
profit_percent,
is_active,
created_at,
updated_at
)
VALUES (
1,
'Test Branch',
'addis_ababa',
4,
2,
1,
TRUE,
0.10,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
COMMIT;

View File

@ -18,17 +18,21 @@ CREATE TABLE IF NOT EXISTS users (
email IS NOT NULL email IS NOT NULL
OR phone_number IS NOT NULL OR phone_number IS NOT NULL
), ),
UNIQUE(email, company_id), UNIQUE (email, company_id),
UNIQUE (phone_number, company_id) UNIQUE (phone_number, company_id)
); );
CREATE TABLE IF NOT EXISTS virtual_game_providers ( CREATE TABLE IF NOT EXISTS virtual_game_providers (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
provider_id VARCHAR(100) UNIQUE NOT NULL, -- providerId from Veli Games provider_id VARCHAR(100) UNIQUE NOT NULL,
provider_name VARCHAR(255) NOT NULL, -- providerName -- providerId from Veli Games
logo_dark TEXT, -- logoForDark (URL) provider_name VARCHAR(255) NOT NULL,
logo_light TEXT, -- logoForLight (URL) -- providerName
enabled BOOLEAN NOT NULL DEFAULT TRUE, -- allow enabling/disabling providers logo_dark TEXT,
-- logoForDark (URL)
logo_light TEXT,
-- logoForLight (URL)
enabled BOOLEAN NOT NULL DEFAULT TRUE,
-- allow enabling/disabling providers
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ updated_at TIMESTAMPTZ
); );
@ -40,7 +44,14 @@ CREATE TABLE IF NOT EXISTS wallets (
is_bettable BOOLEAN NOT NULL, is_bettable BOOLEAN NOT NULL,
is_transferable BOOLEAN NOT NULL, is_transferable BOOLEAN NOT NULL,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
type VARCHAR(255) NOT NULL, type TEXT NOT NULL CHECK (
type IN (
'regular_wallet',
'static_wallet',
'branch_wallet',
'company_wallet'
)
),
is_active BOOLEAN NOT NULL DEFAULT true, is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
@ -96,7 +107,7 @@ CREATE TABLE exchange_rates (
to_currency VARCHAR(3) NOT NULL, to_currency VARCHAR(3) NOT NULL,
rate DECIMAL(19, 6) NOT NULL, rate DECIMAL(19, 6) NOT NULL,
valid_until TIMESTAMP NOT NULL, valid_until TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW (),
UNIQUE (from_currency, to_currency) UNIQUE (from_currency, to_currency)
); );
CREATE TABLE IF NOT EXISTS bet_outcomes ( CREATE TABLE IF NOT EXISTS bet_outcomes (
@ -220,9 +231,9 @@ CREATE TABLE IF NOT EXISTS shop_bets (
cashed_out BOOLEAN DEFAULT FALSE NOT NULL, cashed_out BOOLEAN DEFAULT FALSE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(shop_transaction_id), UNIQUE (shop_transaction_id),
UNIQUE(bet_id), UNIQUE (bet_id),
UNIQUE(cashout_id) UNIQUE (cashout_id)
); );
CREATE TABLE IF NOT EXISTS shop_deposits ( CREATE TABLE IF NOT EXISTS shop_deposits (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -232,7 +243,7 @@ CREATE TABLE IF NOT EXISTS shop_deposits (
branch_wallet_id BIGINT NOT NULL, branch_wallet_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(shop_transaction_id) UNIQUE (shop_transaction_id)
); );
CREATE TABLE IF NOT EXISTS branches ( CREATE TABLE IF NOT EXISTS branches (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -246,7 +257,7 @@ CREATE TABLE IF NOT EXISTS branches (
is_self_owned BOOLEAN NOT NULL DEFAULT false, is_self_owned BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(wallet_id), UNIQUE (wallet_id),
CONSTRAINT profit_percentage_check CHECK ( CONSTRAINT profit_percentage_check CHECK (
profit_percent >= 0 profit_percent >= 0
AND profit_percent < 1 AND profit_percent < 1
@ -263,12 +274,9 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
branch_id BIGINT NOT NULL, branch_id BIGINT NOT NULL,
UNIQUE(user_id, branch_id) UNIQUE (user_id, branch_id)
);
CREATE TABLE IF NOT EXISTS branch_locations (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
); );
CREATE TABLE IF NOT EXISTS branch_locations (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE events ( CREATE TABLE events (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
sport_id INT NOT NULL, sport_id INT NOT NULL,
@ -289,21 +297,15 @@ CREATE TABLE events (
match_period INT, match_period INT,
is_live BOOLEAN NOT NULL DEFAULT false, is_live BOOLEAN NOT NULL DEFAULT false,
status TEXT NOT NULL, status TEXT NOT NULL,
fetched_at TIMESTAMP DEFAULT now(), fetched_at TIMESTAMP DEFAULT now (),
source TEXT NOT NULL DEFAULT 'b365api' CHECK ( source TEXT NOT NULL DEFAULT 'b365api' CHECK (
source IN ( source IN ('b365api', 'bfair', '1xbet', 'bwin', 'enetpulse')
'b365api',
'bfair',
'1xbet',
'bwin',
'enetpulse'
)
), ),
default_is_active BOOLEAN NOT NULL DEFAULT true, default_is_active BOOLEAN NOT NULL DEFAULT true,
default_is_featured BOOLEAN NOT NULL DEFAULT false, default_is_featured BOOLEAN NOT NULL DEFAULT false,
default_winning_upper_limit INT NOT NULL, default_winning_upper_limit INT NOT NULL,
is_monitored BOOLEAN NOT NULL DEFAULT FALSE, is_monitored BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE(id, source) UNIQUE (id, source)
); );
CREATE TABLE event_history ( CREATE TABLE event_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -319,7 +321,7 @@ CREATE TABLE company_event_settings (
is_featured BOOLEAN, is_featured BOOLEAN,
winning_upper_limit INT, winning_upper_limit INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(company_id, event_id) UNIQUE (company_id, event_id)
); );
CREATE TABLE odds_market ( CREATE TABLE odds_market (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -330,13 +332,13 @@ CREATE TABLE odds_market (
market_id TEXT NOT NULL, market_id TEXT NOT NULL,
raw_odds JSONB NOT NULL, raw_odds JSONB NOT NULL,
default_is_active BOOLEAN NOT NULL DEFAULT true, default_is_active BOOLEAN NOT NULL DEFAULT true,
fetched_at TIMESTAMP DEFAULT now(), fetched_at TIMESTAMP DEFAULT now (),
expires_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL,
UNIQUE (event_id, market_id) UNIQUE (event_id, market_id)
); );
CREATE TABLE odd_history ( CREATE TABLE odd_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
odds_market_id BIGINT NOT NULL REFERENCES odds_market(id), odds_market_id BIGINT NOT NULL REFERENCES odds_market (id),
raw_odd_id BIGINT NOT NULL, raw_odd_id BIGINT NOT NULL,
market_id TEXT NOT NULL, market_id TEXT NOT NULL,
event_id TEXT NOT NULL, event_id TEXT NOT NULL,
@ -358,7 +360,7 @@ CREATE TABLE company_odd_settings (
is_active BOOLEAN, is_active BOOLEAN,
custom_raw_odds JSONB, custom_raw_odds JSONB,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(company_id, odds_market_id) UNIQUE (company_id, odds_market_id)
); );
CREATE TABLE result_log ( CREATE TABLE result_log (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -408,7 +410,7 @@ CREATE TABLE company_league_settings (
is_active BOOLEAN, is_active BOOLEAN,
is_featured BOOLEAN, is_featured BOOLEAN,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(league_id, company_id) UNIQUE (league_id, company_id)
); );
CREATE TABLE teams ( CREATE TABLE teams (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -425,7 +427,7 @@ CREATE TABLE IF NOT EXISTS global_settings (
); );
-- Tenant/Company-specific overrides -- Tenant/Company-specific overrides
CREATE TABLE IF NOT EXISTS company_settings ( CREATE TABLE IF NOT EXISTS company_settings (
company_id BIGINT NOT NULL REFERENCES companies(id) ON DELETE CASCADE, company_id BIGINT NOT NULL REFERENCES companies (id) ON DELETE CASCADE,
key TEXT NOT NULL, key TEXT NOT NULL,
value TEXT NOT NULL, value TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@ -439,10 +441,10 @@ CREATE TABLE bonus (
); );
CREATE TABLE flags ( CREATE TABLE flags (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE, bet_id BIGINT REFERENCES bets (id) ON DELETE CASCADE,
odds_market_id BIGINT REFERENCES odds_market(id), odds_market_id BIGINT REFERENCES odds_market (id),
reason TEXT, reason TEXT,
flagged_at TIMESTAMP DEFAULT NOW(), flagged_at TIMESTAMP DEFAULT NOW (),
resolved BOOLEAN DEFAULT FALSE, resolved BOOLEAN DEFAULT FALSE,
-- either bet or odd is flagged (not at the same time) -- either bet or odd is flagged (not at the same time)
CHECK ( CHECK (
@ -458,20 +460,20 @@ CREATE TABLE flags (
); );
CREATE TABLE direct_deposits ( CREATE TABLE direct_deposits (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES users(id), customer_id BIGINT NOT NULL REFERENCES users (id),
wallet_id BIGINT NOT NULL REFERENCES wallets(id), wallet_id BIGINT NOT NULL REFERENCES wallets (id),
amount NUMERIC(15, 2) NOT NULL, amount NUMERIC(15, 2) NOT NULL,
bank_reference TEXT NOT NULL, bank_reference TEXT NOT NULL,
sender_account TEXT NOT NULL, sender_account TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'rejected')), status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'rejected')),
created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW (),
verified_by BIGINT REFERENCES users(id), verified_by BIGINT REFERENCES users (id),
verification_notes TEXT, verification_notes TEXT,
verified_at TIMESTAMP verified_at TIMESTAMP
); );
CREATE INDEX idx_direct_deposits_status ON direct_deposits(status); CREATE INDEX idx_direct_deposits_status ON direct_deposits (status);
CREATE INDEX idx_direct_deposits_customer ON direct_deposits(customer_id); CREATE INDEX idx_direct_deposits_customer ON direct_deposits (customer_id);
CREATE INDEX idx_direct_deposits_reference ON direct_deposits(bank_reference); CREATE INDEX idx_direct_deposits_reference ON direct_deposits (bank_reference);
-- Views -- Views
CREATE VIEW companies_details AS CREATE VIEW companies_details AS
SELECT companies.*, SELECT companies.*,
@ -486,7 +488,7 @@ FROM companies
; ;
CREATE VIEW branch_details AS CREATE VIEW branch_details AS
SELECT branches.*, SELECT branches.*,
CONCAT(users.first_name, ' ', users.last_name) AS manager_name, CONCAT (users.first_name, ' ', users.last_name) AS manager_name,
users.phone_number AS manager_phone_number, users.phone_number AS manager_phone_number,
wallets.balance, wallets.balance,
wallets.is_active AS wallet_is_active wallets.is_active AS wallet_is_active
@ -500,9 +502,9 @@ CREATE TABLE IF NOT EXISTS supported_operations (
); );
CREATE VIEW bet_with_outcomes AS CREATE VIEW bet_with_outcomes AS
SELECT bets.*, SELECT bets.*,
CONCAT(users.first_name, ' ', users.last_name) AS full_name, CONCAT (users.first_name, ' ', users.last_name) AS full_name,
users.phone_number, users.phone_number,
JSON_AGG(bet_outcomes.*) AS outcomes JSON_AGG (bet_outcomes.*) AS outcomes
FROM bets FROM bets
LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id
LEFT JOIN users ON bets.user_id = users.id LEFT JOIN users ON bets.user_id = users.id
@ -512,7 +514,7 @@ GROUP BY bets.id,
users.phone_number; users.phone_number;
CREATE VIEW ticket_with_outcomes AS CREATE VIEW ticket_with_outcomes AS
SELECT tickets.*, SELECT tickets.*,
JSON_AGG(ticket_outcomes.*) AS outcomes JSON_AGG (ticket_outcomes.*) AS outcomes
FROM tickets FROM tickets
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id
GROUP BY tickets.id; GROUP BY tickets.id;
@ -566,7 +568,7 @@ SELECT sb.*,
st.verified AS transaction_verified, st.verified AS transaction_verified,
bets.status, bets.status,
bets.total_odds, bets.total_odds,
JSON_AGG(bet_outcomes.*) AS outcomes JSON_AGG (bet_outcomes.*) AS outcomes
FROM shop_bets AS sb FROM shop_bets AS sb
JOIN shop_transactions st ON st.id = sb.shop_transaction_id JOIN shop_transactions st ON st.id = sb.shop_transaction_id
JOIN bets ON bets.id = sb.bet_id JOIN bets ON bets.id = sb.bet_id
@ -643,47 +645,47 @@ FROM odds_market o
JOIN events e ON o.event_id = e.id; JOIN events e ON o.event_id = e.id;
-- Foreign Keys -- Foreign Keys
ALTER TABLE refresh_tokens ALTER TABLE refresh_tokens
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id);
ALTER TABLE bets ALTER TABLE bets
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users (id);
ALTER TABLE wallets ALTER TABLE wallets
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users (id);
ALTER TABLE customer_wallets ALTER TABLE customer_wallets
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users (id),
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets (id),
ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id); ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets (id);
ALTER TABLE wallet_transfer ALTER TABLE wallet_transfer
ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets (id),
ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets (id),
ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users (id);
ALTER TABLE shop_transactions ALTER TABLE shop_transactions
ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches (id),
ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users (id);
ALTER TABLE shop_bets ALTER TABLE shop_bets
ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions (id),
ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets(id); ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets (id);
ALTER TABLE shop_deposits ALTER TABLE shop_deposits
ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions (id),
ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id); ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users (id);
ALTER TABLE branches ALTER TABLE branches
ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets (id),
ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id), ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users (id),
ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations(key); ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations (key);
ALTER TABLE branch_operations ALTER TABLE branch_operations
ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches (id) ON DELETE CASCADE;
ALTER TABLE branch_cashiers ALTER TABLE branch_cashiers
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches (id) ON DELETE CASCADE;
ALTER TABLE companies ALTER TABLE companies
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users (id),
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE; ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE CASCADE;
ALTER TABLE company_league_settings ALTER TABLE company_league_settings
ADD CONSTRAINT fk_league_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, ADD CONSTRAINT fk_league_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_league_settings_league FOREIGN KEY (league_id) REFERENCES leagues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_league_settings_league FOREIGN KEY (league_id) REFERENCES leagues (id) ON DELETE CASCADE;
ALTER TABLE company_event_settings ALTER TABLE company_event_settings
ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE; ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE;
ALTER TABLE company_odd_settings ALTER TABLE company_odd_settings
ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market(id) ON DELETE CASCADE; ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE;

View File

@ -18,17 +18,19 @@ CREATE TABLE IF NOT EXISTS referral_settings (
); );
CREATE TABLE IF NOT EXISTS referrals ( CREATE TABLE IF NOT EXISTS referrals (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
company_id BIGINT NOT NULL REFERENCES companies (id) ON
DELETE CASCADE,
referral_code VARCHAR(10) NOT NULL UNIQUE, referral_code VARCHAR(10) NOT NULL UNIQUE,
referrer_id VARCHAR(255) NOT NULL, referrer_id BIGINT NOT NULL REFERENCES users (id),
referred_id VARCHAR(255) UNIQUE, referred_id BIGINT UNIQUE REFERENCES users (id),
status ReferralStatus NOT NULL DEFAULT 'PENDING', status ReferralStatus NOT NULL DEFAULT 'PENDING',
reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
cashback_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, cashback_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ NOT NULL -- FOREIGN KEY (referrer_id) REFERENCES users (id),
-- FOREIGN KEY (referrer_id) REFERENCES users (id), -- FOREIGN KEY (referred_id) REFERENCES users (id),
-- FOREIGN KEY (referred_id) REFERENCES users (id), ,
CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0), CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0),
CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0) CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0)
); );
@ -38,7 +40,7 @@ CREATE INDEX idx_referrals_status ON referrals (status);
ALTER TABLE users ALTER TABLE users
ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE, ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE,
ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10); ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10);
-- Modify wallet table to track bonus money separately -- Modify wallet TABLE to track bonus money separately
ALTER TABLE wallets ALTER TABLE wallets
ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,

View File

@ -1,11 +1,11 @@
-- Settings Initial Data -- -- Settings Initial Data
INSERT INTO global_settings (key, value) -- INSERT INTO global_settings (key, value)
VALUES ('sms_provider', 'afro_message'), -- VALUES ('sms_provider', 'afro_message'),
('max_number_of_outcomes', '30'), -- ('max_number_of_outcomes', '30'),
('bet_amount_limit', '10000000'), -- ('bet_amount_limit', '10000000'),
('daily_ticket_limit', '50'), -- ('daily_ticket_limit', '50'),
('total_winnings_limit', '1000000'), -- ('total_winnings_limit', '1000000'),
('amount_for_bet_referral', '1000000'), -- ('amount_for_bet_referral', '1000000'),
('cashback_amount_cap', '1000') ON CONFLICT (key) DO -- ('cashback_amount_cap', '1000') ON CONFLICT (key) DO
UPDATE -- UPDATE
SET value = EXCLUDED.value; -- SET value = EXCLUDED.value;

View File

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

View File

@ -1,218 +1,220 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Users -- -- Users
INSERT INTO users ( -- INSERT INTO users (
id, -- id,
first_name, -- first_name,
last_name, -- last_name,
email, -- email,
phone_number, -- phone_number,
password, -- password,
role, -- role,
email_verified, -- email_verified,
phone_verified, -- phone_verified,
created_at, -- created_at,
updated_at, -- updated_at,
suspended, -- suspended,
company_id -- company_id
) -- )
VALUES ( -- VALUES (
1, -- 1,
'John', -- 'John',
'Doe', -- 'Doe',
'john.doe@example.com', -- 'john.doe@example.com',
NULL, -- NULL,
crypt('password123', gen_salt('bf'))::bytea, -- crypt('password@123', gen_salt('bf'))::bytea,
'customer', -- 'customer',
TRUE, -- TRUE,
FALSE, -- FALSE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
FALSE, -- FALSE,
NULL -- 1
), -- ),
( -- (
2, -- 2,
'Test', -- 'Test',
'Admin', -- 'Admin',
'test.admin@gmail.com', -- 'test.admin@gmail.com',
'0988554466', -- '0988554466',
crypt('password123', gen_salt('bf'))::bytea, -- crypt('password@123', gen_salt('bf'))::bytea,
'admin', -- 'admin',
TRUE, -- TRUE,
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
FALSE, -- FALSE,
1 -- 1
), -- ),
( -- (
3, -- 3,
'Samuel', -- 'Samuel',
'Tariku', -- 'Tariku',
'cybersamt@gmail.com', -- 'cybersamt@gmail.com',
'0911111111', -- '0911111111',
crypt('password@123', gen_salt('bf'))::bytea, -- crypt('password@123', gen_salt('bf'))::bytea,
'super_admin', -- 'super_admin',
TRUE, -- TRUE,
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
FALSE, -- FALSE,
NULL -- NULL
), -- ),
( -- (
4, -- 4,
'Kirubel', -- 'Kirubel',
'Kibru', -- 'Kibru',
'kirubel.jkl679@gmail.com', -- 'kirubel.jkl679@gmail.com',
'0911554486', -- '0911554486',
crypt('password@123', gen_salt('bf'))::bytea, -- crypt('password@123', gen_salt('bf'))::bytea,
'super_admin', -- 'super_admin',
TRUE, -- TRUE,
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
FALSE, -- FALSE,
NULL -- NULL
), -- ),
( -- (
5, -- 5,
'Test', -- 'Test',
'Veli', -- 'Veli',
'test.veli@example.com', -- 'test.veli@example.com',
NULL, -- NULL,
crypt('password@123', gen_salt('bf'))::bytea, -- crypt('password@123', gen_salt('bf'))::bytea,
'customer', -- 'customer',
TRUE, -- TRUE,
FALSE, -- FALSE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
FALSE, -- FALSE,
NULL -- 1
); -- );
-- Supported Operations -- -- Supported Operations
INSERT INTO supported_operations (id, name, description) -- INSERT INTO supported_operations (id, name, description)
VALUES (1, 'SportBook', 'Sportbook operations'), -- VALUES (1, 'SportBook', 'Sportbook operations'),
(2, 'Virtual', 'Virtual operations'); -- (2, 'Virtual', 'Virtual operations');
-- Wallets -- -- Wallets
INSERT INTO wallets ( -- INSERT INTO wallets (
id, -- id,
balance, -- balance,
is_withdraw, -- is_withdraw,
is_bettable, -- is_bettable,
is_transferable, -- is_transferable,
user_id, -- user_id,
type, -- type,
currency, -- currency,
is_active, -- is_active,
created_at, -- created_at,
updated_at -- updated_at
) -- )
VALUES ( -- VALUES (
1, -- 1,
10000, -- 10000,
TRUE, -- TRUE,
TRUE, -- TRUE,
TRUE, -- TRUE,
1, -- 1,
'regular', -- 'regular_wallet',
'ETB', -- 'ETB',
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP -- CURRENT_TIMESTAMP
), -- ),
( -- (
2, -- 2,
5000, -- 5000,
FALSE, -- FALSE,
TRUE, -- TRUE,
TRUE, -- TRUE,
1, -- 1,
'static', -- 'static_wallet',
'ETB', -- 'ETB',
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP -- CURRENT_TIMESTAMP
), -- ),
( -- (
3, -- 3,
20000, -- 20000,
TRUE, -- TRUE,
TRUE, -- TRUE,
TRUE, -- TRUE,
2, -- 2,
'company_main', -- 'company_wallet',
'ETB', -- 'ETB',
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP -- CURRENT_TIMESTAMP
), -- ),
( -- (
4, -- 4,
15000, -- 15000,
TRUE, -- TRUE,
TRUE, -- TRUE,
TRUE, -- TRUE,
2, -- 2,
'branch_main', -- 'branch_wallet',
'ETB', -- 'ETB',
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP -- CURRENT_TIMESTAMP
); -- );
-- Customer Wallets -- -- Customer Wallets
INSERT INTO customer_wallets ( -- INSERT INTO customer_wallets (
id, -- id,
customer_id, -- customer_id,
regular_wallet_id, -- regular_wallet_id,
static_wallet_id -- static_wallet_id
) -- )
VALUES (1, 1, 1, 2); -- VALUES (1, 1, 1, 2);
-- Company -- -- Company
INSERT INTO companies ( -- INSERT INTO companies (
id, -- id,
name, -- name,
admin_id, -- slug,
wallet_id, -- admin_id,
deducted_percentage, -- wallet_id,
is_active, -- deducted_percentage,
created_at, -- is_active,
updated_at -- created_at,
) -- updated_at
VALUES ( -- )
1, -- VALUES (
'Test Company', -- 1,
2, -- 'FortuneBets',
3, -- 'fortunebets',
0.10, -- 2,
TRUE, -- 3,
CURRENT_TIMESTAMP, -- 0.10,
CURRENT_TIMESTAMP -- TRUE,
); -- CURRENT_TIMESTAMP,
-- Branch -- CURRENT_TIMESTAMP
INSERT INTO branches ( -- );
id, -- -- Branch
name, -- INSERT INTO branches (
location, -- id,
wallet_id, -- name,
branch_manager_id, -- location,
company_id, -- wallet_id,
is_self_owned, -- branch_manager_id,
profit_percent, -- company_id,
is_active, -- is_self_owned,
created_at, -- profit_percent,
updated_at -- is_active,
) -- created_at,
VALUES ( -- updated_at
1, -- )
'Test Branch', -- VALUES (
'addis_ababa', -- 1,
4, -- 'Test Branch',
2, -- 'addis_ababa',
1, -- 4,
TRUE, -- 2,
0.10, -- 1,
TRUE, -- TRUE,
CURRENT_TIMESTAMP, -- 0.10,
CURRENT_TIMESTAMP -- TRUE,
); -- CURRENT_TIMESTAMP,
-- CURRENT_TIMESTAMP
-- );

View File

@ -1,77 +1,87 @@
-- name: CreateReferral :one -- name: CreateReferral :one
INSERT INTO referrals ( INSERT INTO referrals (
referral_code, referral_code,
referrer_id, referrer_id,
status, company_id,
reward_amount, status,
expires_at reward_amount,
) VALUES ( expires_at
$1, $2, $3, $4, $5 )
) RETURNING *; VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *;
-- name: GetReferralByCode :one -- name: GetReferralByCode :one
SELECT * FROM referrals SELECT *
FROM referrals
WHERE referral_code = $1; WHERE referral_code = $1;
-- name: UpdateReferral :one -- name: UpdateReferral :one
UPDATE referrals UPDATE referrals
SET SET referred_id = $2,
referred_id = $2, status = $3,
status = $3, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING *; RETURNING *;
-- name: UpdateReferralCode :exec -- name: UpdateReferralCode :exec
UPDATE users UPDATE users
SET SET referral_code = $2,
referral_code = $2, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1; WHERE id = $1;
-- name: GetReferralStats :one -- name: GetReferralStats :one
SELECT SELECT COUNT(*) AS total_referrals,
COUNT(*) as total_referrals, COUNT(
COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals, CASE
COALESCE(SUM(reward_amount), 0) as total_reward_earned, WHEN status = 'COMPLETED' THEN 1
COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards END
) AS completed_referrals,
COALESCE(SUM(reward_amount), 0) AS total_reward_earned,
COALESCE(
SUM(
CASE
WHEN status = 'PENDING' THEN reward_amount
END
),
0
) AS pending_rewards
FROM referrals FROM referrals
WHERE referrer_id = $1; WHERE referrer_id = $1
AND company_id = $2;
-- name: GetReferralSettings :one -- name: GetReferralSettings :one
SELECT * FROM referral_settings SELECT *
FROM referral_settings
LIMIT 1; LIMIT 1;
-- name: UpdateReferralSettings :one -- name: UpdateReferralSettings :one
UPDATE referral_settings UPDATE referral_settings
SET SET referral_reward_amount = $2,
referral_reward_amount = $2, cashback_percentage = $3,
cashback_percentage = $3, bet_referral_bonus_percentage = $4,
bet_referral_bonus_percentage= $4, max_referrals = $5,
max_referrals = $5, expires_after_days = $6,
expires_after_days = $6, updated_by = $7,
updated_by = $7, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING *; RETURNING *;
-- name: CreateReferralSettings :one -- name: CreateReferralSettings :one
INSERT INTO referral_settings ( INSERT INTO referral_settings (
referral_reward_amount, referral_reward_amount,
cashback_percentage, cashback_percentage,
max_referrals, max_referrals,
bet_referral_bonus_percentage, bet_referral_bonus_percentage,
expires_after_days, expires_after_days,
updated_by updated_by
) VALUES ( )
$1, $2, $3, $4, $5, $6 VALUES ($1, $2, $3, $4, $5, $6)
) RETURNING *; RETURNING *;
-- name: GetReferralByReferredID :one -- name: GetReferralByReferredID :one
SELECT * FROM referrals WHERE referred_id = $1 LIMIT 1; SELECT *
FROM referrals
WHERE referred_id = $1
LIMIT 1;
-- name: GetActiveReferralByReferrerID :one -- name: GetActiveReferralByReferrerID :one
SELECT * FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1; SELECT *
FROM referrals
WHERE referrer_id = $1
AND status = 'PENDING'
LIMIT 1;
-- name: GetReferralCountByID :one -- name: GetReferralCountByID :one
SELECT count(*) FROM referrals WHERE referrer_id = $1; SELECT COUNT(*)
FROM referrals
WHERE referrer_id = $1;

View File

@ -193,7 +193,7 @@ SET password = $1,
WHERE ( WHERE (
email = $2 email = $2
OR phone_number = $3 OR phone_number = $3
AND company_id = $4 AND company_id = $5
); );
-- name: GetAdminByCompanyID :one -- name: GetAdminByCompanyID :one
SELECT users.* SELECT users.*

View File

@ -540,9 +540,10 @@ type Otp struct {
type Referral struct { type Referral struct {
ID int64 `json:"id"` ID int64 `json:"id"`
CompanyID int64 `json:"company_id"`
ReferralCode string `json:"referral_code"` ReferralCode string `json:"referral_code"`
ReferrerID string `json:"referrer_id"` ReferrerID int64 `json:"referrer_id"`
ReferredID pgtype.Text `json:"referred_id"` ReferredID pgtype.Int8 `json:"referred_id"`
Status Referralstatus `json:"status"` Status Referralstatus `json:"status"`
RewardAmount pgtype.Numeric `json:"reward_amount"` RewardAmount pgtype.Numeric `json:"reward_amount"`
CashbackAmount pgtype.Numeric `json:"cashback_amount"` CashbackAmount pgtype.Numeric `json:"cashback_amount"`

View File

@ -13,19 +13,21 @@ import (
const CreateReferral = `-- name: CreateReferral :one const CreateReferral = `-- name: CreateReferral :one
INSERT INTO referrals ( INSERT INTO referrals (
referral_code, referral_code,
referrer_id, referrer_id,
status, company_id,
reward_amount, status,
expires_at reward_amount,
) VALUES ( expires_at
$1, $2, $3, $4, $5 )
) RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, company_id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
` `
type CreateReferralParams struct { type CreateReferralParams struct {
ReferralCode string `json:"referral_code"` ReferralCode string `json:"referral_code"`
ReferrerID string `json:"referrer_id"` ReferrerID int64 `json:"referrer_id"`
CompanyID int64 `json:"company_id"`
Status Referralstatus `json:"status"` Status Referralstatus `json:"status"`
RewardAmount pgtype.Numeric `json:"reward_amount"` RewardAmount pgtype.Numeric `json:"reward_amount"`
ExpiresAt pgtype.Timestamptz `json:"expires_at"` ExpiresAt pgtype.Timestamptz `json:"expires_at"`
@ -35,6 +37,7 @@ func (q *Queries) CreateReferral(ctx context.Context, arg CreateReferralParams)
row := q.db.QueryRow(ctx, CreateReferral, row := q.db.QueryRow(ctx, CreateReferral,
arg.ReferralCode, arg.ReferralCode,
arg.ReferrerID, arg.ReferrerID,
arg.CompanyID,
arg.Status, arg.Status,
arg.RewardAmount, arg.RewardAmount,
arg.ExpiresAt, arg.ExpiresAt,
@ -42,6 +45,7 @@ func (q *Queries) CreateReferral(ctx context.Context, arg CreateReferralParams)
var i Referral var i Referral
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CompanyID,
&i.ReferralCode, &i.ReferralCode,
&i.ReferrerID, &i.ReferrerID,
&i.ReferredID, &i.ReferredID,
@ -57,15 +61,15 @@ func (q *Queries) CreateReferral(ctx context.Context, arg CreateReferralParams)
const CreateReferralSettings = `-- name: CreateReferralSettings :one const CreateReferralSettings = `-- name: CreateReferralSettings :one
INSERT INTO referral_settings ( INSERT INTO referral_settings (
referral_reward_amount, referral_reward_amount,
cashback_percentage, cashback_percentage,
max_referrals, max_referrals,
bet_referral_bonus_percentage, bet_referral_bonus_percentage,
expires_after_days, expires_after_days,
updated_by updated_by
) VALUES ( )
$1, $2, $3, $4, $5, $6 VALUES ($1, $2, $3, $4, $5, $6)
) RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version
` `
type CreateReferralSettingsParams struct { type CreateReferralSettingsParams struct {
@ -103,14 +107,19 @@ func (q *Queries) CreateReferralSettings(ctx context.Context, arg CreateReferral
} }
const GetActiveReferralByReferrerID = `-- name: GetActiveReferralByReferrerID :one const GetActiveReferralByReferrerID = `-- name: GetActiveReferralByReferrerID :one
SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1 SELECT id, company_id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
FROM referrals
WHERE referrer_id = $1
AND status = 'PENDING'
LIMIT 1
` `
func (q *Queries) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (Referral, error) { func (q *Queries) GetActiveReferralByReferrerID(ctx context.Context, referrerID int64) (Referral, error) {
row := q.db.QueryRow(ctx, GetActiveReferralByReferrerID, referrerID) row := q.db.QueryRow(ctx, GetActiveReferralByReferrerID, referrerID)
var i Referral var i Referral
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CompanyID,
&i.ReferralCode, &i.ReferralCode,
&i.ReferrerID, &i.ReferrerID,
&i.ReferredID, &i.ReferredID,
@ -125,7 +134,8 @@ func (q *Queries) GetActiveReferralByReferrerID(ctx context.Context, referrerID
} }
const GetReferralByCode = `-- name: GetReferralByCode :one const GetReferralByCode = `-- name: GetReferralByCode :one
SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals SELECT id, company_id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
FROM referrals
WHERE referral_code = $1 WHERE referral_code = $1
` `
@ -134,6 +144,7 @@ func (q *Queries) GetReferralByCode(ctx context.Context, referralCode string) (R
var i Referral var i Referral
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CompanyID,
&i.ReferralCode, &i.ReferralCode,
&i.ReferrerID, &i.ReferrerID,
&i.ReferredID, &i.ReferredID,
@ -148,14 +159,18 @@ func (q *Queries) GetReferralByCode(ctx context.Context, referralCode string) (R
} }
const GetReferralByReferredID = `-- name: GetReferralByReferredID :one const GetReferralByReferredID = `-- name: GetReferralByReferredID :one
SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referred_id = $1 LIMIT 1 SELECT id, company_id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
FROM referrals
WHERE referred_id = $1
LIMIT 1
` `
func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype.Text) (Referral, error) { func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype.Int8) (Referral, error) {
row := q.db.QueryRow(ctx, GetReferralByReferredID, referredID) row := q.db.QueryRow(ctx, GetReferralByReferredID, referredID)
var i Referral var i Referral
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CompanyID,
&i.ReferralCode, &i.ReferralCode,
&i.ReferrerID, &i.ReferrerID,
&i.ReferredID, &i.ReferredID,
@ -170,10 +185,12 @@ func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype
} }
const GetReferralCountByID = `-- name: GetReferralCountByID :one const GetReferralCountByID = `-- name: GetReferralCountByID :one
SELECT count(*) FROM referrals WHERE referrer_id = $1 SELECT COUNT(*)
FROM referrals
WHERE referrer_id = $1
` `
func (q *Queries) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { func (q *Queries) GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error) {
row := q.db.QueryRow(ctx, GetReferralCountByID, referrerID) row := q.db.QueryRow(ctx, GetReferralCountByID, referrerID)
var count int64 var count int64
err := row.Scan(&count) err := row.Scan(&count)
@ -181,7 +198,8 @@ func (q *Queries) GetReferralCountByID(ctx context.Context, referrerID string) (
} }
const GetReferralSettings = `-- name: GetReferralSettings :one const GetReferralSettings = `-- name: GetReferralSettings :one
SELECT id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version FROM referral_settings SELECT id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version
FROM referral_settings
LIMIT 1 LIMIT 1
` `
@ -204,15 +222,31 @@ func (q *Queries) GetReferralSettings(ctx context.Context) (ReferralSetting, err
} }
const GetReferralStats = `-- name: GetReferralStats :one const GetReferralStats = `-- name: GetReferralStats :one
SELECT SELECT COUNT(*) AS total_referrals,
COUNT(*) as total_referrals, COUNT(
COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals, CASE
COALESCE(SUM(reward_amount), 0) as total_reward_earned, WHEN status = 'COMPLETED' THEN 1
COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards END
) AS completed_referrals,
COALESCE(SUM(reward_amount), 0) AS total_reward_earned,
COALESCE(
SUM(
CASE
WHEN status = 'PENDING' THEN reward_amount
END
),
0
) AS pending_rewards
FROM referrals FROM referrals
WHERE referrer_id = $1 WHERE referrer_id = $1
AND company_id = $2
` `
type GetReferralStatsParams struct {
ReferrerID int64 `json:"referrer_id"`
CompanyID int64 `json:"company_id"`
}
type GetReferralStatsRow struct { type GetReferralStatsRow struct {
TotalReferrals int64 `json:"total_referrals"` TotalReferrals int64 `json:"total_referrals"`
CompletedReferrals int64 `json:"completed_referrals"` CompletedReferrals int64 `json:"completed_referrals"`
@ -220,8 +254,8 @@ type GetReferralStatsRow struct {
PendingRewards interface{} `json:"pending_rewards"` PendingRewards interface{} `json:"pending_rewards"`
} }
func (q *Queries) GetReferralStats(ctx context.Context, referrerID string) (GetReferralStatsRow, error) { func (q *Queries) GetReferralStats(ctx context.Context, arg GetReferralStatsParams) (GetReferralStatsRow, error) {
row := q.db.QueryRow(ctx, GetReferralStats, referrerID) row := q.db.QueryRow(ctx, GetReferralStats, arg.ReferrerID, arg.CompanyID)
var i GetReferralStatsRow var i GetReferralStatsRow
err := row.Scan( err := row.Scan(
&i.TotalReferrals, &i.TotalReferrals,
@ -234,17 +268,16 @@ func (q *Queries) GetReferralStats(ctx context.Context, referrerID string) (GetR
const UpdateReferral = `-- name: UpdateReferral :one const UpdateReferral = `-- name: UpdateReferral :one
UPDATE referrals UPDATE referrals
SET SET referred_id = $2,
referred_id = $2, status = $3,
status = $3, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at RETURNING id, company_id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
` `
type UpdateReferralParams struct { type UpdateReferralParams struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ReferredID pgtype.Text `json:"referred_id"` ReferredID pgtype.Int8 `json:"referred_id"`
Status Referralstatus `json:"status"` Status Referralstatus `json:"status"`
} }
@ -253,6 +286,7 @@ func (q *Queries) UpdateReferral(ctx context.Context, arg UpdateReferralParams)
var i Referral var i Referral
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.CompanyID,
&i.ReferralCode, &i.ReferralCode,
&i.ReferrerID, &i.ReferrerID,
&i.ReferredID, &i.ReferredID,
@ -268,9 +302,8 @@ func (q *Queries) UpdateReferral(ctx context.Context, arg UpdateReferralParams)
const UpdateReferralCode = `-- name: UpdateReferralCode :exec const UpdateReferralCode = `-- name: UpdateReferralCode :exec
UPDATE users UPDATE users
SET SET referral_code = $2,
referral_code = $2, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
` `
@ -286,14 +319,13 @@ func (q *Queries) UpdateReferralCode(ctx context.Context, arg UpdateReferralCode
const UpdateReferralSettings = `-- name: UpdateReferralSettings :one const UpdateReferralSettings = `-- name: UpdateReferralSettings :one
UPDATE referral_settings UPDATE referral_settings
SET SET referral_reward_amount = $2,
referral_reward_amount = $2, cashback_percentage = $3,
cashback_percentage = $3, bet_referral_bonus_percentage = $4,
bet_referral_bonus_percentage= $4, max_referrals = $5,
max_referrals = $5, expires_after_days = $6,
expires_after_days = $6, updated_by = $7,
updated_by = $7, updated_at = CURRENT_TIMESTAMP
updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version
` `

View File

@ -587,7 +587,7 @@ SET password = $1,
WHERE ( WHERE (
email = $2 email = $2
OR phone_number = $3 OR phone_number = $3
AND company_id = $4 AND company_id = $5
) )
` `
@ -596,6 +596,7 @@ type UpdatePasswordParams struct {
Email pgtype.Text `json:"email"` Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"` PhoneNumber pgtype.Text `json:"phone_number"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error { func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
@ -604,6 +605,7 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams)
arg.Email, arg.Email,
arg.PhoneNumber, arg.PhoneNumber,
arg.UpdatedAt, arg.UpdatedAt,
arg.CompanyID,
) )
return err return err
} }

View File

@ -10,3 +10,10 @@ type RefreshToken struct {
CreatedAt time.Time CreatedAt time.Time
Revoked bool Revoked bool
} }
// I used this because i was getting an error with the ValidInt64
// when it was being unmarshaled by the jwt
type NullJwtInt64 struct {
Value int64
Valid bool
}

View File

@ -62,8 +62,9 @@ type ReferralSettingsReq struct {
type Referral struct { type Referral struct {
ID int64 ID int64
ReferralCode string ReferralCode string
ReferrerID string ReferrerID int64
ReferredID *string CompanyID int64
ReferredID *int64
Status ReferralStatus Status ReferralStatus
RewardAmount float64 RewardAmount float64
CashbackAmount float64 CashbackAmount float64

View File

@ -3,16 +3,17 @@ package domain
type Role string type Role string
const ( const (
RoleSuperAdmin Role = "super_admin" RoleSuperAdmin Role = "super_admin"
RoleAdmin Role = "admin" RoleAdmin Role = "admin"
RoleBranchManager Role = "branch_manager" RoleBranchManager Role = "branch_manager"
RoleCustomer Role = "customer" RoleCustomer Role = "customer"
RoleCashier Role = "cashier" RoleCashier Role = "cashier"
RoleTransactionApprover Role = "transaction_approver"
) )
func (r Role) IsValid() bool { func (r Role) IsValid() bool {
switch r { switch r {
case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier: case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier, RoleTransactionApprover:
return true return true
default: default:
return false return false

View File

@ -23,7 +23,7 @@ type User struct {
UpdatedAt time.Time UpdatedAt time.Time
SuspendedAt time.Time SuspendedAt time.Time
Suspended bool Suspended bool
CompanyID ValidInt64 //This should be null CompanyID ValidInt64
} }
type UserFilter struct { type UserFilter struct {
@ -36,7 +36,6 @@ type UserFilter struct {
CreatedAfter ValidTime CreatedAfter ValidTime
} }
type RegisterUserReq struct { type RegisterUserReq struct {
FirstName string FirstName string
LastName string LastName string
@ -65,6 +64,7 @@ type ResetPasswordReq struct {
Password string Password string
Otp string Otp string
OtpMedium OtpMedium OtpMedium OtpMedium
CompanyID int64
} }
type UpdateUserReq struct { type UpdateUserReq struct {
UserId int64 UserId int64

View File

@ -76,9 +76,10 @@ type CreateCustomerWallet struct {
type WalletType string type WalletType string
const ( const (
CustomerWalletType WalletType = "customer_wallet" RegularWalletType WalletType = "regular_wallet"
BranchWalletType WalletType = "branch_wallet" StaticWalletType WalletType = "static_wallet"
CompanyWalletType WalletType = "company_wallet" BranchWalletType WalletType = "branch_wallet"
CompanyWalletType WalletType = "company_wallet"
) )
// domain/wallet.go // domain/wallet.go
@ -92,18 +93,18 @@ const (
) )
type DirectDeposit struct { type DirectDeposit struct {
ID int64 ID int64 `json:"id"`
CustomerID int64 CustomerID int64 `json:"customer_id"`
WalletID int64 WalletID int64 `json:"wallet_id"`
Wallet Wallet // Joined data Wallet Wallet `json:"wallet"`
Amount Currency Amount Currency `json:"amount"`
BankReference string BankReference string `json:"bank_reference"`
SenderAccount string SenderAccount string `json:"sender_account"`
Status DirectDepositStatus Status DirectDepositStatus `json:"status"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
VerifiedBy *int64 // Nullable VerifiedBy *int64 `json:"verified_by"`
VerificationNotes string VerificationNotes string `json:"verification_notes"`
VerifiedAt *time.Time // Nullable VerifiedAt *time.Time `json:"verified_at"`
} }
type CreateDirectDeposit struct { type CreateDirectDeposit struct {
@ -124,14 +125,14 @@ type UpdateDirectDeposit struct {
} }
type DirectDepositRequest struct { type DirectDepositRequest struct {
CustomerID int64 `json:"customer_id" binding:"required"` CustomerID int64 `json:"customer_id" binding:"required"`
Amount Currency `json:"amount" binding:"required,gt=0"` Amount Currency `json:"amount" binding:"required,gt=0"`
BankReference string `json:"bank_reference" binding:"required"` BankReference string `json:"bank_reference" binding:"required"`
SenderAccount string `json:"sender_account" binding:"required"` SenderAccount string `json:"sender_account" binding:"required"`
} }
type VerifyDirectDepositRequest struct { type VerifyDirectDepositRequest struct {
DepositID int64 `json:"deposit_id" binding:"required"` DepositID int64 `json:"deposit_id" binding:"required"`
IsVerified bool `json:"is_verified" binding:"required"` IsVerified bool `json:"is_verified" binding:"required"`
Notes string `json:"notes"` Notes string `json:"notes"`
} }

View File

@ -16,13 +16,13 @@ type ReferralRepository interface {
CreateReferral(ctx context.Context, referral *domain.Referral) error CreateReferral(ctx context.Context, referral *domain.Referral) error
GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error) GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error)
UpdateReferral(ctx context.Context, referral *domain.Referral) error UpdateReferral(ctx context.Context, referral *domain.Referral) error
GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error)
GetSettings(ctx context.Context) (*domain.ReferralSettings, error) GetSettings(ctx context.Context) (*domain.ReferralSettings, error)
UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error
CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error
GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) // New method GetReferralByReferredID(ctx context.Context, referredID int64) (*domain.Referral, error)
GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error)
GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) GetActiveReferralByReferrerID(ctx context.Context, referrerID int64) (*domain.Referral, error)
UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error
} }
@ -58,6 +58,7 @@ func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Refe
Status: dbgen.Referralstatus(referral.Status), Status: dbgen.Referralstatus(referral.Status),
RewardAmount: rewardAmount, RewardAmount: rewardAmount,
ExpiresAt: pgtype.Timestamptz{Time: referral.ExpiresAt, Valid: true}, ExpiresAt: pgtype.Timestamptz{Time: referral.ExpiresAt, Valid: true},
CompanyID: referral.CompanyID,
} }
_, err := r.store.queries.CreateReferral(ctx, params) _, err := r.store.queries.CreateReferral(ctx, params)
@ -76,9 +77,9 @@ func (r *ReferralRepo) GetReferralByCode(ctx context.Context, code string) (*dom
} }
func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Referral) error { func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Referral) error {
var referredID pgtype.Text var referredID pgtype.Int8
if referral.ReferredID != nil { if referral.ReferredID != nil {
referredID = pgtype.Text{String: *referral.ReferredID, Valid: true} referredID = pgtype.Int8{Int64: *referral.ReferredID, Valid: true}
} }
params := dbgen.UpdateReferralParams{ params := dbgen.UpdateReferralParams{
@ -91,8 +92,11 @@ func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Refe
return err return err
} }
func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) { func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error) {
stats, err := r.store.queries.GetReferralStats(ctx, userID) stats, err := r.store.queries.GetReferralStats(ctx, dbgen.GetReferralStatsParams{
ReferrerID: userID,
CompanyID: companyID,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -175,8 +179,8 @@ func (r *ReferralRepo) CreateSettings(ctx context.Context, settings *domain.Refe
return err return err
} }
func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) { func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID int64) (*domain.Referral, error) {
dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Text{String: referredID, Valid: true}) dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Int8{Int64: referredID, Valid: true})
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, nil return nil, nil
@ -186,7 +190,7 @@ func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID s
return r.mapToDomainReferral(&dbReferral), nil return r.mapToDomainReferral(&dbReferral), nil
} }
func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error) {
count, err := r.store.queries.GetReferralCountByID(ctx, referrerID) count, err := r.store.queries.GetReferralCountByID(ctx, referrerID)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@ -198,7 +202,7 @@ func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID stri
return count, nil return count, nil
} }
func (r *ReferralRepo) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) { func (r *ReferralRepo) GetActiveReferralByReferrerID(ctx context.Context, referrerID int64) (*domain.Referral, error) {
referral, err := r.store.queries.GetActiveReferralByReferrerID(ctx, referrerID) referral, err := r.store.queries.GetActiveReferralByReferrerID(ctx, referrerID)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@ -211,9 +215,9 @@ func (r *ReferralRepo) GetActiveReferralByReferrerID(ctx context.Context, referr
} }
func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral { func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral {
var referredID *string var referredID *int64
if dbRef.ReferredID.Valid { if dbRef.ReferredID.Valid {
referredID = &dbRef.ReferredID.String referredID = &dbRef.ReferredID.Int64
} }
rewardAmount := 0.0 rewardAmount := 0.0

View File

@ -428,7 +428,7 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string, companyID d
}, nil }, nil
} }
func (s *Store) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error { func (s *Store) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error {
err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{ err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
ID: usedOtpId, ID: usedOtpId,
UsedAt: pgtype.Timestamptz{ UsedAt: pgtype.Timestamptz{
@ -449,6 +449,10 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password
String: identifier, String: identifier,
Valid: true, Valid: true,
}, },
CompanyID: pgtype.Int8{
Int64: companyId,
Valid: true,
},
}) })
if err != nil { if err != nil {
return err return err

View File

@ -8,13 +8,13 @@ import (
type ReferralStore interface { type ReferralStore interface {
GenerateReferralCode() (string, error) GenerateReferralCode() (string, error)
CreateReferral(ctx context.Context, userID int64) error CreateReferral(ctx context.Context, userID int64, companyID int64) error
ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error
ProcessDepositBonus(ctx context.Context, userID string, amount float64) error ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error
GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error
GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error)
CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error
UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error
GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error)
GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error)
ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error
} }

View File

@ -52,11 +52,11 @@ func (s *Service) GenerateReferralCode() (string, error) {
return code, nil return code, nil
} }
func (s *Service) CreateReferral(ctx context.Context, userID int64) error { func (s *Service) CreateReferral(ctx context.Context, userID int64, companyID int64) error {
s.logger.Info("Creating referral code for user", "userID", userID) s.logger.Info("Creating referral code for user", "userID", userID)
// check if user already has an active referral code // check if user already has an active referral code
referral, err := s.repo.GetActiveReferralByReferrerID(ctx, fmt.Sprintf("%d", userID)) referral, err := s.repo.GetActiveReferralByReferrerID(ctx, userID)
if err != nil { if err != nil {
s.logger.Error("Failed to check if user alredy has active referral code", "error", err) s.logger.Error("Failed to check if user alredy has active referral code", "error", err)
return err return err
@ -73,7 +73,7 @@ func (s *Service) CreateReferral(ctx context.Context, userID int64) error {
} }
// check referral count limit // check referral count limit
referralCount, err := s.GetReferralCountByID(ctx, fmt.Sprintf("%d", userID)) referralCount, err := s.GetReferralCountByID(ctx, userID)
if err != nil { if err != nil {
s.logger.Error("Failed to get referral count", "userID", userID, "error", err) s.logger.Error("Failed to get referral count", "userID", userID, "error", err)
return err return err
@ -96,10 +96,11 @@ func (s *Service) CreateReferral(ctx context.Context, userID int64) error {
if err := s.repo.CreateReferral(ctx, &domain.Referral{ if err := s.repo.CreateReferral(ctx, &domain.Referral{
ReferralCode: code, ReferralCode: code,
ReferrerID: fmt.Sprintf("%d", userID), ReferrerID: userID,
Status: domain.ReferralPending, Status: domain.ReferralPending,
RewardAmount: rewardAmount, RewardAmount: rewardAmount,
ExpiresAt: expireDuration, ExpiresAt: expireDuration,
CompanyID: companyID,
}); err != nil { }); err != nil {
return err return err
} }
@ -138,7 +139,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
return ErrInvalidReferralSignup return ErrInvalidReferralSignup
} }
referral.ReferredID = &referredPhone referral.ReferredID = &user.ID
referral.Status = domain.ReferralCompleted referral.Status = domain.ReferralCompleted
referral.UpdatedAt = time.Now() referral.UpdatedAt = time.Now()
@ -147,13 +148,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
return err return err
} }
referrerId, err := strconv.Atoi(referral.ReferrerID) wallets, err := s.store.GetCustomerWallet(ctx, referral.ReferrerID)
if err != nil {
s.logger.Error("Failed to convert referrer id", "referrerId", referral.ReferrerID, "error", err)
return err
}
wallets, err := s.store.GetCustomerWallet(ctx, int64(referrerId))
if err != nil { if err != nil {
s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err) s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err)
return err return err
@ -161,7 +156,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID,
domain.ToCurrency(float32(referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, domain.ToCurrency(float32(referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
fmt.Sprintf("Added %v to static wallet because of referral ID %v", referral.RewardAmount, referrerId), fmt.Sprintf("Added %v to static wallet because of referral ID %v", referral.RewardAmount, referral.ReferrerID),
) )
if err != nil { if err != nil {
s.logger.Error("Failed to add referral reward to static wallet", "walletID", wallets.StaticID, "referrer phone number", referredPhone, "error", err) s.logger.Error("Failed to add referral reward to static wallet", "walletID", wallets.StaticID, "referrer phone number", referredPhone, "error", err)
@ -212,8 +207,8 @@ func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amo
return nil return nil
} }
func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error { func (s *Service) ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error {
s.logger.Info("Processing bet referral", "userPhone", userPhone, "betAmount", betAmount) s.logger.Info("Processing bet referral", "userID", userId, "betAmount", betAmount)
settings, err := s.repo.GetSettings(ctx) settings, err := s.repo.GetSettings(ctx)
if err != nil { if err != nil {
@ -221,29 +216,24 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
return err return err
} }
referral, err := s.repo.GetReferralByReferredID(ctx, userPhone) referral, err := s.repo.GetReferralByReferredID(ctx, userId)
if err != nil { if err != nil {
s.logger.Error("Failed to get referral by referred ID", "userPhone", userPhone, "error", err) s.logger.Error("Failed to get referral by referred ID", "userId", userId, "error", err)
return err return err
} }
if referral == nil || referral.Status != domain.ReferralCompleted { if referral == nil || referral.Status != domain.ReferralCompleted {
s.logger.Warn("No valid referral found", "userPhone", userPhone, "status", referral.Status) s.logger.Warn("No valid referral found", "userId", userId, "status", referral.Status)
return ErrNoReferralFound return ErrNoReferralFound
} }
referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64)
if err != nil {
s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err)
return errors.New("invalid referrer phone number format")
}
wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) wallets, err := s.walletSvc.GetWalletsByUser(ctx, referral.ReferrerID)
if err != nil { if err != nil {
s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err) s.logger.Error("Failed to get wallets for referrer", "referrerID", referral.ReferrerID, "error", err)
return err return err
} }
if len(wallets) == 0 { if len(wallets) == 0 {
s.logger.Error("Referrer has no wallet", "referrerID", referrerID) s.logger.Error("Referrer has no wallet", "referrerID", referral.ReferrerID)
return errors.New("referrer has no wallet") return errors.New("referrer has no wallet")
} }
@ -260,24 +250,24 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount)) fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount))
if err != nil { if err != nil {
s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err) s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referral.ReferrerID, "bonus", bonus, "error", err)
return err return err
} }
s.logger.Info("Bet referral processed successfully", "userPhone", userPhone, "referrerID", referrerID, "bonus", bonus) s.logger.Info("Bet referral processed successfully", "referrer ID", referral.ReferrerID, "referrerID", referral.ReferrerID, "bonus", bonus)
return nil return nil
} }
func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) { func (s *Service) GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error) {
s.logger.Info("Fetching referral stats", "userPhone", userPhone) s.logger.Info("Fetching referral stats", "userID", userID)
stats, err := s.repo.GetReferralStats(ctx, userPhone) stats, err := s.repo.GetReferralStats(ctx, userID, companyID)
if err != nil { if err != nil {
s.logger.Error("Failed to get referral stats", "userPhone", userPhone, "error", err) s.logger.Error("Failed to get referral stats", "userID", userID, "error", err)
return nil, err return nil, err
} }
s.logger.Info("Referral stats retrieved successfully", "userPhone", userPhone, "totalReferrals", stats.TotalReferrals) s.logger.Info("Referral stats retrieved successfully", "userID", userID, "totalReferrals", stats.TotalReferrals)
return stats, nil return stats, nil
} }
@ -328,7 +318,7 @@ func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSett
return settings, nil return settings, nil
} }
func (s *Service) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { func (s *Service) GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error) {
count, err := s.repo.GetReferralCountByID(ctx, referrerID) count, err := s.repo.GetReferralCountByID(ctx, referrerID)
if err != nil { if err != nil {
s.logger.Error("Failed to get referral count", "userID", referrerID, "error", err) s.logger.Error("Failed to get referral count", "userID", referrerID, "error", err)

View File

@ -23,7 +23,7 @@ type UserStore interface {
GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error) GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error) GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error // identifier verified email or phone UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error) GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error)

View File

@ -58,7 +58,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo
} }
// reset pass and mark otp as used // reset pass and mark otp as used
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID) err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID, resetReq.CompanyID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -156,10 +156,10 @@ func SetupReportandVirtualGameCronJobs(
spec string spec string
period string period string
}{ }{
{ // {
spec: "*/60 * * * * *", // Every 1 minute for testing // spec: "*/60 * * * * *", // Every 1 minute for testing
period: "test", // period: "test",
}, // },
{ {
spec: "0 0 0 * * *", // Daily at midnight spec: "0 0 0 * * *", // Daily at midnight
period: "daily", period: "daily",
@ -254,8 +254,6 @@ func SetupReportandVirtualGameCronJobs(
log.Printf("Cron jobs started. Reports will be saved to: %s", outputDir) log.Printf("Cron jobs started. Reports will be saved to: %s", outputDir)
} }
func ProcessBetCashback(ctx context.Context, betService *betSvc.Service) { func ProcessBetCashback(ctx context.Context, betService *betSvc.Service) {
c := cron.New(cron.WithSeconds()) c := cron.New(cron.WithSeconds())

View File

@ -196,11 +196,13 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
Valid: true, Valid: true,
} }
} }
companyFilter := int64(c.QueryInt("company_id"))
filter := domain.UserFilter{ filter := domain.UserFilter{
Role: string(domain.RoleAdmin), Role: string(domain.RoleAdmin),
CompanyID: domain.ValidInt64{ CompanyID: domain.ValidInt64{
Value: int64(c.QueryInt("company_id")), Value: companyFilter,
Valid: false, Valid: companyFilter != 0,
}, },
Page: domain.ValidInt{ Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1, Value: c.QueryInt("page", 1) - 1,

View File

@ -107,7 +107,13 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusForbidden, "Only customers are allowed to login ") return fiber.NewError(fiber.StatusForbidden, "Only customers are allowed to login ")
} }
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(
successRes.UserId,
successRes.Role,
successRes.CompanyID,
h.jwtConfig.JwtAccessKey,
h.jwtConfig.JwtAccessExpiry,
);
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to create access token", h.mongoLoggerSvc.Error("Failed to create access token",
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),

View File

@ -379,9 +379,110 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet [get] // @Router /api/v1/{tenant_slug}/sport/bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error { func (h *Handler) GetAllBet(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
// companyID := c.Locals("company_id").(domain.ValidInt64)
// 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.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")
}
isShopBet = domain.ValidBool{
Value: isShopBetParse,
Valid: true,
}
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_before format",
zap.String("time", createdBeforeQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
IsShopBet: isShopBet,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
})
if err != nil {
h.mongoLoggerSvc.Error("Failed to get all bets",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets፡"+err.Error())
}
res := make([]domain.BetRes, len(bets))
for i, bet := range bets {
res[i] = domain.ConvertBet(bet)
}
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
}
// GetAllTenants godoc
// @Summary Gets all bets
// @Description Gets all the bets
// @Tags bet
// @Accept json
// @Produce json
// @Success 200 {array} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet [get]
func (h *Handler) GetAllTenantBets(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id", zap.Any("company_id", companyID))
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} }
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
@ -485,8 +586,54 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes // @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet/{id} [get] // @Router /api/v1/sport/bet/{id} [get]
func (h *Handler) GetBetByID(c *fiber.Ctx) error { func (h *Handler) GetBetByID(c *fiber.Ctx) error {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
h.mongoLoggerSvc.Info("Invalid bet ID",
zap.String("betID", betID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
bet, err := h.betSvc.GetBetByID(c.Context(), id)
if err != nil {
h.mongoLoggerSvc.Info("Failed to get bet by ID",
zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusNotFound),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet")
}
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)
}
// GetTenantBetByID godoc
// @Summary Gets bet by id
// @Description Gets a single bet by id
// @Tags bet
// @Accept json
// @Produce json
// @Param id path int true "Bet ID"
// @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet/{id} [get]
func (h *Handler) GetTenantBetByID(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id")
@ -696,8 +843,52 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet/{id} [delete] // @Router /api/v1/sport/bet/{id} [delete]
func (h *Handler) DeleteBet(c *fiber.Ctx) error { func (h *Handler) DeleteBet(c *fiber.Ctx) error {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
h.mongoLoggerSvc.Error("Invalid bet ID",
zap.String("betID", betID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
err = h.betSvc.SetBetToRemoved(c.Context(), id)
if err != nil {
h.mongoLoggerSvc.Error("Failed to delete bet by ID",
zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet:"+err.Error())
}
h.mongoLoggerSvc.Info("Bet removed successfully",
zap.Int64("betID", id),
zap.Int("status_code", fiber.StatusOK),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil)
}
// DeleteTenantBet godoc
// @Summary Deletes bet by id
// @Description Deletes bet by id
// @Tags bet
// @Accept json
// @Produce json
// @Param id path int true "Bet ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/sport/bet/{id} [delete]
func (h *Handler) DeleteTenantBet(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id")

View File

@ -254,6 +254,7 @@ func (h *Handler) CreateBranchOperation(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Branch Operation Created", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Branch Operation Created", nil, nil)
} }
// GetBranchByID godoc // GetBranchByID godoc
// @Summary Gets branch by id // @Summary Gets branch by id
// @Description Gets a single branch by id // @Description Gets a single branch by id
@ -295,6 +296,108 @@ func (h *Handler) GetBranchByID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Branch retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Branch retrieved successfully", res, nil)
} }
// ReturnBranchWallet godoc
// @Summary Unassign the branch wallet to company
// @Description Unassign the branch wallet to company
// @Tags branch
// @Accept json
// @Produce json
// @Param id path int true "Branch ID"
// @Success 200 {object} domain.BranchDetailRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/branch/{id}/return [post]
func (h *Handler) ReturnBranchWallet(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
branchID := c.Params("id")
id, err := strconv.ParseInt(branchID, 10, 64)
if err != nil {
h.mongoLoggerSvc.Info("Invalid branch ID",
zap.String("branch", branchID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid branch ID")
}
branch, err := h.branchSvc.GetBranchByID(c.Context(), id)
if err != nil {
h.mongoLoggerSvc.Info("Failed to get branch by ID",
zap.Int64("branchID", id),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
company, err := h.companySvc.GetCompanyByID(c.Context(), branch.CompanyID)
if err != nil {
h.mongoLoggerSvc.Info("Failed to get company by ID",
zap.Int64("branchID", id),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
branchWallet, err := h.walletSvc.GetWalletByID(c.Context(), branch.WalletID)
if err != nil {
h.mongoLoggerSvc.Info("Failed to get wallet by ID",
zap.Int64("branchID", id),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
_, err = h.walletSvc.AddToWallet(c.Context(),
company.WalletID,
branchWallet.Balance,
domain.ValidInt64{Value: userID, Valid: true},
domain.TRANSFER_DIRECT,
domain.PaymentDetails{},
fmt.Sprintf("Returning Branch %v Wallet to Company", branch.Name))
if err != nil {
h.mongoLoggerSvc.Info("Failed to add to company wallet",
zap.Int64("branchID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
_, err = h.walletSvc.DeductFromWallet(c.Context(),
branchWallet.ID,
branchWallet.Balance,
domain.ValidInt64{Value: userID, Valid: true},
domain.TRANSFER_DIRECT,
"Branch Wallet Balance has been returned to Company",
)
if err != nil {
h.mongoLoggerSvc.Info("Failed to deduct from branch wallet",
zap.Int64("branchID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
res := domain.ConvertBranchDetail(branch)
return response.WriteJSON(c, fiber.StatusOK, "Branch Wallet Has been emptied", res, nil)
}
// GetBranchByManagerID godoc // GetBranchByManagerID godoc
// @Summary Gets branches by manager id // @Summary Gets branches by manager id
// @Description Gets a branches by manager id // @Description Gets a branches by manager id

View File

@ -26,6 +26,8 @@ type CustomersRes struct {
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
CompanyID int64 `json:"company_id"`
CompanyName string `json:"company_name"`
} }
// GetAllCustomers godoc // GetAllCustomers godoc
@ -156,6 +158,30 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
"Failed to retrieve user last login:"+err.Error()) "Failed to retrieve user last login:"+err.Error())
} }
} }
if !customer.CompanyID.Valid {
h.mongoLoggerSvc.Error("Invalid user company ID",
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 company id for customer:")
}
company, err := h.companySvc.GetCompanyByID(c.Context(), customer.CompanyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Invalid user company value",
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 fetch company for customer:")
}
result[index] = CustomersRes{ result[index] = CustomersRes{
ID: customer.ID, ID: customer.ID,
FirstName: customer.FirstName, FirstName: customer.FirstName,
@ -170,6 +196,176 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
SuspendedAt: customer.SuspendedAt, SuspendedAt: customer.SuspendedAt,
Suspended: customer.Suspended, Suspended: customer.Suspended,
LastLogin: *lastLogin, LastLogin: *lastLogin,
CompanyID: company.ID,
CompanyName: company.Name,
}
}
return response.WritePaginatedJSON(c, fiber.StatusOK, "Customers retrieved successfully", result, nil, filter.Page.Value, int(total))
}
// GetAllTenantCustomers godoc
// @Summary Get all Customers
// @Description Get all Customers
// @Tags customer
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {object} CustomersRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/tenant/{tenant_slug}/customer [get]
func (h *Handler) GetAllTenantCustomers(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_before format",
zap.String("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,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("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,
Valid: true,
}
}
filter := domain.UserFilter{
Role: string(domain.RoleCustomer),
CompanyID: companyID,
Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1,
Valid: true,
},
PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10),
Valid: true,
},
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
}
valErrs, ok := h.validator.Validate(c, filter)
if !ok {
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.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))
for index, customer := range customers {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), customer.ID)
if err != nil {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &customer.CreatedAt
} else {
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())
}
}
if !customer.CompanyID.Valid {
h.mongoLoggerSvc.Error("Invalid user company ID",
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 company id for customer:")
}
company, err := h.companySvc.GetCompanyByID(c.Context(), customer.CompanyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Invalid user company value",
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 fetch company for customer:")
}
result[index] = CustomersRes{
ID: customer.ID,
FirstName: customer.FirstName,
LastName: customer.LastName,
Email: customer.Email,
PhoneNumber: customer.PhoneNumber,
Role: customer.Role,
EmailVerified: customer.EmailVerified,
PhoneVerified: customer.PhoneVerified,
CreatedAt: customer.CreatedAt,
UpdatedAt: customer.UpdatedAt,
SuspendedAt: customer.SuspendedAt,
Suspended: customer.Suspended,
LastLogin: *lastLogin,
CompanyID: company.ID,
CompanyName: company.Name,
} }
} }
@ -222,6 +418,29 @@ func (h *Handler) GetCustomerByID(c *fiber.Ctx) error {
lastLogin = &user.CreatedAt lastLogin = &user.CreatedAt
} }
if !user.CompanyID.Valid {
h.mongoLoggerSvc.Error("Invalid user company ID",
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 company id for customer:")
}
company, err := h.companySvc.GetCompanyByID(c.Context(), user.CompanyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Invalid user company value",
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 fetch company for customer:")
}
res := CustomersRes{ res := CustomersRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
@ -236,16 +455,226 @@ func (h *Handler) GetCustomerByID(c *fiber.Ctx) error {
SuspendedAt: user.SuspendedAt, SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin, LastLogin: *lastLogin,
CompanyID: company.ID,
CompanyName: company.Name,
} }
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
} }
// GetCustomerByID godoc
// @Summary Get customer by id
// @Description Get a single customer by id
// @Tags customer
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} CustomersRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/tenant/{tenant_slug}/customer/{id} [get]
func (h *Handler) GetTenantCustomerByID(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid customers ID")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
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.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
}
if !user.CompanyID.Valid || user.CompanyID.Value != companyID.Value {
h.mongoLoggerSvc.Error("Failed to get customer",
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 customer:"+err.Error())
}
company, err := h.companySvc.GetCompanyByID(c.Context(), user.CompanyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Invalid user company value",
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 fetch company for customer:")
}
res := CustomersRes{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
PhoneNumber: user.PhoneNumber,
Role: user.Role,
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
CompanyID: company.ID,
CompanyName: company.Name,
}
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
}
// GetTenantCustomerBets godoc
// @Summary Get tenant customer bets
// @Description Get tenant customer bets
// @Tags customer
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} CustomersRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/tenant/{tenant_slug}/customer/{id}/bets [get]
func (h *Handler) GetTenantCustomerBets(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid customers ID")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
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())
}
if !user.CompanyID.Valid || user.CompanyID.Value != companyID.Value {
h.mongoLoggerSvc.Warn("User Attempt to access another companies customer",
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 customer bet")
}
bets, err := h.betSvc.GetBetByUserID(c.Context(), user.ID)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get user 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 get user bet:"+err.Error())
}
res := make([]domain.BetRes, len(bets))
for i, bet := range bets {
res[i] = domain.ConvertBet(bet)
}
return response.WriteJSON(c, fiber.StatusOK, "User's Bets retrieved successfully", res, nil)
}
// GetCustomerBets godoc
// @Summary Get customer bets
// @Description Get customer bets
// @Tags customer
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} CustomersRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/customer/{id}/bets [get]
func (h *Handler) GetCustomerBets(c *fiber.Ctx) error {
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid customers ID")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
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())
}
bets, err := h.betSvc.GetBetByUserID(c.Context(), user.ID)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get user 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 get user bet:"+err.Error())
}
res := make([]domain.BetRes, len(bets))
for i, bet := range bets {
res[i] = domain.ConvertBet(bet)
}
return response.WriteJSON(c, fiber.StatusOK, "User's Bets retrieved successfully", res, nil)
}
type updateCustomerReq struct { type updateCustomerReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"` LastName string `json:"last_name" example:"Doe"`
Suspended bool `json:"suspended" example:"false"` Suspended bool `json:"suspended" example:"false"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
} }
// UpdateCustomers godoc // UpdateCustomers godoc
@ -341,3 +770,108 @@ func (h *Handler) UpdateCustomer(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Customers updated successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Customers updated successfully", nil, nil)
} }
// UpdateTenantCustomer godoc
// @Summary Update Customers
// @Description Update Customers
// @Tags customer
// @Accept json
// @Produce json
// @Param Customers body updateCustomerReq true "Update Customers"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/tenant/{tenant_slug}/customer/{id} [put]
func (h *Handler) UpdateTenantCustomer(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
var req updateCustomerReq
if err := c.BodyParser(&req); err != 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 {
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.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")
}
user, err := h.userSvc.GetUserByID(c.Context(), CustomersId)
if err != nil {
h.mongoLoggerSvc.Info("Customers Not Found",
zap.String("userID", CustomersIdStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Customers Not Found")
}
if user.CompanyID.Value != companyID.Value {
h.mongoLoggerSvc.Warn("User Attempt to update another companies customer",
zap.String("userID", CustomersIdStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Customers Not Found")
}
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: CustomersId,
FirstName: domain.ValidString{
Value: req.FirstName,
Valid: req.FirstName != "",
},
LastName: domain.ValidString{
Value: req.LastName,
Valid: req.LastName != "",
},
Suspended: domain.ValidBool{
Value: req.Suspended,
Valid: true,
},
},
)
if err != 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

@ -11,6 +11,11 @@ import (
) )
func (h *Handler) CreateReferralCode(c *fiber.Ctx) error { func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.mongoLoggerSvc.Info("Invalid user ID in context", h.mongoLoggerSvc.Info("Invalid user ID in context",
@ -21,7 +26,7 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification") return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
if err := h.referralSvc.CreateReferral(c.Context(), userID); err != nil { if err := h.referralSvc.CreateReferral(c.Context(), userID, companyID.Value); err != nil {
h.mongoLoggerSvc.Error("Failed to create referral", h.mongoLoggerSvc.Error("Failed to create referral",
zap.Int64("userID", userID), zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
@ -35,6 +40,7 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
} }
func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error { func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
var req domain.ReferralSettingsReq var req domain.ReferralSettingsReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse settings", h.mongoLoggerSvc.Info("Failed to parse settings",
@ -91,6 +97,57 @@ func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil)
} }
// func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
// companyID := c.Locals("company_id").(domain.ValidInt64)
// if !companyID.Valid {
// h.BadRequestLogger().Error("invalid company id")
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
// }
// userID, ok := c.Locals("user_id").(int64)
// if !ok || userID == 0 {
// h.mongoLoggerSvc.Error("Invalid user ID in context",
// zap.Int64("userID", userID),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id")
// }
// user, err := h.userSvc.GetUserByID(c.Context(), userID)
// if err != nil {
// 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.CompanyID.Valid || user.CompanyID.Value != companyID.Value {
// h.mongoLoggerSvc.Warn("User attempt to login to different company",
// zap.Int64("userID", userID),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
// }
// // referrals, err := h.referralSvc.GetReferralStats(c.Context(), user.ID)
// if err != nil {
// h.mongoLoggerSvc.Error("Failed to get user referrals",
// 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 referral codes")
// }
// }
// GetReferralStats godoc // GetReferralStats godoc
// @Summary Get referral statistics // @Summary Get referral statistics
// @Description Retrieves referral statistics for the authenticated user // @Description Retrieves referral statistics for the authenticated user
@ -101,8 +158,13 @@ func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security Bearer // @Security Bearer
// @Router /api/v1/referral/stats [get] // @Router /api/v1/tenant/{tenant_slug}/referral/stats [get]
func (h *Handler) GetReferralStats(c *fiber.Ctx) error { func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.mongoLoggerSvc.Error("Invalid user ID in context", h.mongoLoggerSvc.Error("Invalid user ID in context",
@ -124,7 +186,17 @@ func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
} }
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.PhoneNumber) if !user.CompanyID.Valid || user.CompanyID.Value != companyID.Value {
h.mongoLoggerSvc.Warn("User attempt to login to different company",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
}
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.ID, companyID.Value)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get referral stats", h.mongoLoggerSvc.Error("Failed to get referral stats",
zap.Int64("userID", userID), zap.Int64("userID", userID),
@ -227,33 +299,9 @@ func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
// h.logger.Error("Invalid user ID in context") // h.logger.Error("Invalid user ID in context")
// return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") // return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
// } // }
userID := int64(2)
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
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()) settings, err := h.referralSvc.GetReferralSettings(c.Context())
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get referral settings", h.mongoLoggerSvc.Error("Failed to get referral settings",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),

View File

@ -0,0 +1,438 @@
package handlers
import (
"fmt"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"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 CreateTransactionApproverReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
CompanyID int64 `json:"company_id" example:"1"`
}
// CreateTransactionApprover godoc
// @Summary Create transaction approver
// @Description Create transaction approver
// @Tags admin
// @Accept json
// @Produce json
// @Param manger body CreateTransactionApproverReq true "Create transaction approver"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/admin [post]
func (h *Handler) CreateTransactionApprover(c *fiber.Ctx) error {
var companyID domain.ValidInt64
var req CreateTransactionApproverReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("failed to parse CreateAdmin request",
zap.Int64("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
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 fiber.NewError(fiber.StatusBadRequest, errMsg)
}
_, err := h.companySvc.GetCompanyByID(c.Context(), req.CompanyID)
if err != nil {
h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
zap.Int64("status_code", fiber.StatusInternalServerError),
zap.Int64("company_id", req.CompanyID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
}
companyID = domain.ValidInt64{
Value: req.CompanyID,
Valid: true,
}
user := domain.CreateUserReq{
FirstName: req.FirstName,
LastName: req.LastName,
Email: req.Email,
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Role: string(domain.RoleTransactionApprover),
CompanyID: companyID,
}
newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil {
h.mongoLoggerSvc.Error("failed to create admin user",
zap.Int64("status_code", fiber.StatusInternalServerError),
zap.Any("request", req),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error())
}
h.mongoLoggerSvc.Info("transaction_approver created successfully",
zap.Int64("transaction_approver_id", newUser.ID),
zap.String("email", newUser.Email),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Transaction Approver created successfully", nil, nil)
}
type TransactionApproverRes struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Role domain.Role `json:"role"`
EmailVerified bool `json:"email_verified"`
PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"`
}
// GetAllAdmins godoc
// @Summary Get all Admins
// @Description Get all Admins
// @Tags admin
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {object} AdminRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/t-approver [get]
func (h *Handler) GetAllTransactionApprovers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
var companyIDFilter domain.ValidInt64
if role == domain.RoleSuperAdmin {
companyIDQuery := int64(c.QueryInt("company_id"))
companyIDFilter = domain.ValidInt64{
Value: companyIDQuery,
Valid: companyIDQuery != 0,
}
} else {
if !companyID.Valid {
h.logger.Info("invalid companyID")
return fiber.NewError(fiber.StatusBadRequest, "Unable to get company ID")
}
companyIDFilter = companyID
}
filter := domain.UserFilter{
Role: string(domain.RoleTransactionApprover),
CompanyID: companyIDFilter,
Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1,
Valid: true,
},
PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10),
Valid: true,
},
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
}
valErrs, ok := h.validator.Validate(c, filter)
if !ok {
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 fiber.NewError(fiber.StatusBadRequest, errMsg)
}
users, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil {
h.mongoLoggerSvc.Error("failed to get users from user service",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Any("filter", filter),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get users"+err.Error())
}
result := make([]TransactionApproverRes, len(users))
for index, admin := range users {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
if err != nil {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &admin.CreatedAt
} else {
h.mongoLoggerSvc.Error("failed to get last login for admin",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("admin_id", admin.ID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
}
}
result[index] = TransactionApproverRes{
ID: admin.ID,
FirstName: admin.FirstName,
LastName: admin.LastName,
Email: admin.Email,
PhoneNumber: admin.PhoneNumber,
Role: admin.Role,
EmailVerified: admin.EmailVerified,
PhoneVerified: admin.PhoneVerified,
CreatedAt: admin.CreatedAt,
UpdatedAt: admin.UpdatedAt,
SuspendedAt: admin.SuspendedAt,
Suspended: admin.Suspended,
LastLogin: *lastLogin,
}
}
h.mongoLoggerSvc.Info("approvers retrieved successfully",
zap.Int("status_code", fiber.StatusOK),
zap.Int("count", len(result)),
zap.Int("page", filter.Page.Value+1),
zap.Time("timestamp", time.Now()),
)
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total))
}
// GetAdminByID godoc
// @Summary Get admin by id
// @Description Get a single admin by id
// @Tags admin
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} AdminRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/t-approver/{id} [get]
func (h *Handler) GetTransactionApproverByID(c *fiber.Ctx) error {
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil {
h.mongoLoggerSvc.Error("invalid admin ID param",
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("param", userIDstr),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid admin ID")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.mongoLoggerSvc.Error("failed to fetch admin by ID",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("admin_id", userID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admin"+err.Error())
}
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil && err != authentication.ErrRefreshTokenNotFound {
h.mongoLoggerSvc.Error("failed to get admin last login",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("admin_id", user.ID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
}
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &user.CreatedAt
}
res := TransactionApproverRes{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
PhoneNumber: user.PhoneNumber,
Role: user.Role,
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
}
h.mongoLoggerSvc.Info("admin retrieved successfully",
zap.Int("status_code", fiber.StatusOK),
zap.Int64("admin_id", user.ID),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Admin retrieved successfully", res, nil)
}
type updateTransactionApproverReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Suspended bool `json:"suspended" example:"false"`
}
// UpdateAdmin godoc
// @Summary Update Admin
// @Description Update Admin
// @Tags admin
// @Accept json
// @Produce json
// @Param admin body updateAdminReq true "Update Admin"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/t-approver/{id} [put]
func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
var req updateTransactionApproverReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed - invalid request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
}
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 fiber.NewError(fiber.StatusBadRequest, errMsg)
}
ApproverIDStr := c.Params("id")
ApproverID, err := strconv.ParseInt(ApproverIDStr, 10, 64)
if err != nil {
h.mongoLoggerSvc.Info("UpdateAdmin failed - invalid Admin ID param",
zap.Int("status_code", fiber.StatusBadRequest),
zap.String("admin_id_param", ApproverIDStr),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID")
}
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: ApproverID,
FirstName: domain.ValidString{
Value: req.FirstName,
Valid: req.FirstName != "",
},
LastName: domain.ValidString{
Value: req.LastName,
Valid: req.LastName != "",
},
Suspended: domain.ValidBool{
Value: req.Suspended,
Valid: true,
},
})
if err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("admin_id", ApproverID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin:"+err.Error())
}
h.mongoLoggerSvc.Info("UpdateAdmin succeeded",
zap.Int("status_code", fiber.StatusOK),
zap.Int64("admin_id", ApproverID),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil)
}

View File

@ -143,13 +143,13 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
} }
type RegisterUserReq struct { type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"` LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"` Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"` ReferralCode string `json:"referral_code" example:"ABC123"`
} }
// RegisterUser godoc // RegisterUser godoc
@ -193,7 +193,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
Password: req.Password, Password: req.Password,
Otp: req.Otp, Otp: req.Otp,
ReferralCode: req.ReferalCode, ReferralCode: req.ReferralCode,
OtpMedium: domain.OtpMediumEmail, OtpMedium: domain.OtpMediumEmail,
CompanyID: companyID, CompanyID: companyID,
Role: string(domain.RoleCustomer), Role: string(domain.RoleCustomer),
@ -247,12 +247,12 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet:"+err.Error())
} }
if req.ReferalCode != "" { if req.ReferralCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode, companyID.Value) err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferralCode, companyID.Value)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to process referral during registration", h.mongoLoggerSvc.Error("Failed to process referral during registration",
zap.String("phone", req.PhoneNumber), zap.String("phone", req.PhoneNumber),
zap.String("code", req.ReferalCode), zap.String("code", req.ReferralCode),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
@ -293,8 +293,70 @@ type ResetCodeReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/sendResetCode [post] // @Router /api/v1/user/sendResetCode [post]
func (h *Handler) SendResetCode(c *fiber.Ctx) error { func (h *Handler) SendResetCode(c *fiber.Ctx) error {
var req ResetCodeReq
if err := c.BodyParser(&req); err != nil {
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 {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
var sentTo string
var medium domain.OtpMedium
if req.Email != "" {
sentTo = req.Email
medium = domain.OtpMediumEmail
} else if req.PhoneNumber != "" {
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, domain.AfroMessage, domain.ValidInt64{}); err != nil {
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)
}
// SendTenantResetCode godoc
// @Summary Send reset code
// @Description Send reset code
// @Tags user
// @Accept json
// @Produce json
// @Param resetCode body ResetCodeReq true "Send reset code"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/sendResetCode [post]
func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id")
@ -367,7 +429,7 @@ type ResetPasswordReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/resetPassword [post] // @Router /api/v1/user/resetPassword [post]
func (h *Handler) ResetPassword(c *fiber.Ctx) error { func (h *Handler) ResetPassword(c *fiber.Ctx) error {
var req ResetPasswordReq var req ResetPasswordReq
@ -421,6 +483,76 @@ func (h *Handler) ResetPassword(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
} }
// ResetTenantPassword godoc
// @Summary Reset tenant password
// @Description Reset tenant password
// @Tags user
// @Accept json
// @Produce json
// @Param resetPassword body ResetPasswordReq true "Reset password"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/resetPassword [post]
func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil {
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 {
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.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())
}
resetReq := domain.ResetPasswordReq{
Email: req.Email,
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Otp: req.Otp,
OtpMedium: medium,
CompanyID: companyID.Value,
}
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
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)
}
type UserProfileRes struct { type UserProfileRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
@ -503,6 +635,7 @@ func (h *Handler) CustomerProfile(c *fiber.Ctx) error {
lastLogin = &user.CreatedAt lastLogin = &user.CreatedAt
} }
res := CustomerProfileRes{ res := CustomerProfileRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,

View File

@ -2,6 +2,7 @@ package jwtutil
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -15,22 +16,23 @@ var (
ErrRefreshTokenNotFound = errors.New("refresh token not found") ErrRefreshTokenNotFound = errors.New("refresh token not found")
) )
type UserClaim struct { type UserClaim struct {
jwt.RegisteredClaims jwt.RegisteredClaims
UserId int64 UserId int64
Role domain.Role Role domain.Role
CompanyID domain.ValidInt64 CompanyID domain.NullJwtInt64
} }
type PopOKClaim struct { type PopOKClaim struct {
jwt.RegisteredClaims jwt.RegisteredClaims
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
Username string `json:"username"` Username string `json:"username"`
Currency string `json:"currency"` Currency string `json:"currency"`
Lang string `json:"lang"` Lang string `json:"lang"`
Mode string `json:"mode"` Mode string `json:"mode"`
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
CompanyID domain.ValidInt64 `json:"company_id"` CompanyID domain.NullJwtInt64 `json:"company_id"`
} }
type JwtConfig struct { type JwtConfig struct {
@ -41,15 +43,18 @@ type JwtConfig struct {
func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) { func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
Issuer: "github.com/lafetz/snippitstash", Issuer: "fortune-bet",
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
Audience: jwt.ClaimStrings{"fortune.com"}, Audience: jwt.ClaimStrings{"api.fortunebets.net"},
NotBefore: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)),
}, },
UserId: userId, UserId: userId,
Role: Role, Role: Role,
CompanyID: CompanyID, CompanyID: domain.NullJwtInt64{
Value: CompanyID.Value,
Valid: CompanyID.Valid,
},
}) })
jwtToken, err := token.SignedString([]byte(key)) jwtToken, err := token.SignedString([]byte(key))
return jwtToken, err return jwtToken, err
@ -70,7 +75,10 @@ func CreatePopOKJwt(userID int64, CompanyID domain.ValidInt64, username, currenc
Lang: lang, Lang: lang,
Mode: mode, Mode: mode,
SessionID: sessionID, SessionID: sessionID,
CompanyID: CompanyID, CompanyID: domain.NullJwtInt64{
Value: CompanyID.Value,
Valid: CompanyID.Valid,
},
}) })
return token.SignedString([]byte(key)) return token.SignedString([]byte(key))
} }
@ -84,6 +92,7 @@ func ParseJwt(jwtToken string, key string) (*UserClaim, error) {
return nil, ErrExpiredToken return nil, ErrExpiredToken
} }
if errors.Is(err, jwt.ErrTokenMalformed) { if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Printf("error %v", err.Error())
return nil, ErrMalformedToken return nil, ErrMalformedToken
} }
return nil, err return nil, err

View File

@ -2,6 +2,7 @@ package httpserver
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"time" "time"
@ -56,6 +57,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
zap.String("ip_address", ip), zap.String("ip_address", ip),
zap.String("user_agent", userAgent), zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
zap.Error(err),
) )
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token")
} }
@ -79,7 +81,10 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
} }
c.Locals("user_id", claim.UserId) c.Locals("user_id", claim.UserId)
c.Locals("role", claim.Role) c.Locals("role", claim.Role)
c.Locals("company_id", claim.CompanyID) c.Locals("company_id", domain.ValidInt64{
Value: claim.CompanyID.Value,
Valid: claim.CompanyID.Valid,
})
c.Locals("refresh_token", refreshToken) c.Locals("refresh_token", refreshToken)
var branchID domain.ValidInt64 var branchID domain.ValidInt64
@ -198,6 +203,7 @@ func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
zap.String("ip_address", ip), zap.String("ip_address", ip),
zap.String("user_agent", userAgent), zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
zap.Error(err),
) )
return fiber.NewError(fiber.StatusUnauthorized, "Invalid token") return fiber.NewError(fiber.StatusUnauthorized, "Invalid token")
} }
@ -222,17 +228,20 @@ func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
} }
func (a *App) TenantMiddleware(c *fiber.Ctx) error { func (a *App) TenantMiddleware(c *fiber.Ctx) error {
if tokenCID, ok := c.Locals("company_id").(domain.ValidInt64); ok && tokenCID.Valid {
return c.Next()
}
tenantSlug := c.Params("tenant_slug") tenantSlug := c.Params("tenant_slug")
if tenantSlug == "" { if tenantSlug == "" {
a.mongoLoggerSvc.Info("blank tenant param",
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "tenant is required for this route") return fiber.NewError(fiber.StatusBadRequest, "tenant is required for this route")
} }
companyID, err := a.companySvc.GetCompanyIDBySlug(c.Context(), tenantSlug) companyID, err := a.companySvc.GetCompanyIDBySlug(c.Context(), tenantSlug)
if err != nil { if err != nil {
a.mongoLoggerSvc.Info("failed to resolve tenant",
zap.String("tenant_slug", tenantSlug),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "failed to resolve tenant") return fiber.NewError(fiber.StatusBadRequest, "failed to resolve tenant")
} }
@ -242,3 +251,35 @@ func (a *App) TenantMiddleware(c *fiber.Ctx) error {
}) })
return c.Next() return c.Next()
} }
func (a *App) TenantAuthMiddleware(c *fiber.Ctx) error {
slugID, ok := c.Locals("tenant_id").(domain.ValidInt64)
if !ok || !slugID.Valid {
a.mongoLoggerSvc.Info("invalid tenant slug",
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid tenant slug")
}
tokenCID, ok := c.Locals("company_id").(domain.ValidInt64)
if !ok || !tokenCID.Valid {
a.mongoLoggerSvc.Error("invalid company id in token",
zap.Time("timestamp", time.Now()),
zap.Bool("tokenCID Valid", tokenCID.Valid),
zap.Bool("ValidInt64 Type Check", ok),
)
return fiber.NewError(fiber.StatusInternalServerError, "invalid company id in token")
}
if slugID.Value != tokenCID.Value {
a.mongoLoggerSvc.Error("token company-id doesn't match the slug company_id",
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "invalid company_id")
}
fmt.Printf("\nTenant successfully authenticated!\n")
return c.Next()
}

View File

@ -62,16 +62,41 @@ func (a *App) initAppRoutes() {
}) })
}) })
a.fiber.Get("/routes", func(c *fiber.Ctx) error {
return c.JSON(a.fiber.Stack()) // prints all registered routes
})
// Groups // Groups
groupV1 := a.fiber.Group("/api/v1") groupV1 := a.fiber.Group("/api/v1")
tenant := groupV1.Group("/:tenant_slug", a.TenantMiddleware) tenant := groupV1.Group("/tenant/:tenant_slug", a.TenantMiddleware)
tenantAuth := groupV1.Group("/:tenant_slug", a.authMiddleware, a.TenantMiddleware) tenant.Get("/test", a.authMiddleware, a.authMiddleware, func(c *fiber.Ctx) error {
fmt.Printf("\nTest Route %v\n", c.Route().Path)
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
fmt.Printf("In the tenant auth test \n")
return c.JSON(fiber.Map{
"message": "Is is fine",
})
})
tenant.Get("/", func(c *fiber.Ctx) error {
fmt.Printf("\nTenant Route %v\n", c.Route().Path)
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
return c.JSON(fiber.Map{
"message": "Company Tenant Active",
})
})
//Direct_deposit //Direct_deposit
groupV1.Post("/direct_deposit", a.authMiddleware, h.InitiateDirectDeposit) groupV1.Post("/direct_deposit", a.authMiddleware, h.InitiateDirectDeposit)
groupV1.Post("/direct_deposit/verify", a.authMiddleware, h.VerifyDirectDeposit) groupV1.Post("/direct_deposit/verify", a.authMiddleware, h.VerifyDirectDeposit)
groupV1.Get("/direct_deposit/pending", a.authMiddleware, h.GetPendingDirectDeposits) groupV1.Get("/direct_deposit/pending", a.authMiddleware, h.GetPendingDirectDeposits)
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
@ -141,28 +166,29 @@ func (a *App) initAppRoutes() {
// groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler) // groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
// groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler) // groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
// User Routes // User Routes
tenant.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/resetPassword", h.ResetPassword)
tenant.Post("/user/sendResetCode", h.SendResetCode) groupV1.Post("/user/sendResetCode", h.SendResetCode)
tenant.Post("/user/resetPassword", h.ResetTenantPassword)
tenant.Post("/user/sendResetCode", h.SendTenantResetCode)
tenant.Post("/user/register", h.RegisterUser) tenant.Post("/user/register", h.RegisterUser)
tenant.Post("/user/sendRegisterCode", h.SendRegisterCode) tenant.Post("/user/sendRegisterCode", h.SendRegisterCode)
tenant.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist) tenant.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
tenantAuth.Get("/user/customer-profile", h.CustomerProfile)
tenantAuth.Get("/user/admin-profile", h.AdminProfile) tenant.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile)
tenantAuth.Get("/user/bets", h.GetBetByUserID) tenant.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
tenant.Get("/user/bets", a.authMiddleware, h.GetBetByUserID)
groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID) groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend) groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser) groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
tenantAuth.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet) tenant.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
tenantAuth.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone) tenant.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
// Referral Routes // Referral Routes
groupV1.Post("/referral/create", a.authMiddleware, h.CreateReferralCode) tenant.Post("/referral/create", a.authMiddleware, h.CreateReferralCode)
groupV1.Get("/referral/stats", a.authMiddleware, h.GetReferralStats) tenant.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
groupV1.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings) groupV1.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings)
groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings) groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings)
groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
@ -177,15 +203,26 @@ func (a *App) initAppRoutes() {
groupV1.Post("/cashiers", a.authMiddleware, h.CreateCashier) groupV1.Post("/cashiers", a.authMiddleware, h.CreateCashier)
groupV1.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier) groupV1.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier)
tenant.Get("/customer", a.authMiddleware, h.GetAllTenantCustomers)
tenant.Get("/customer/:id", a.authMiddleware, h.GetTenantCustomerByID)
tenant.Put("/customer/:id", a.authMiddleware, h.UpdateTenantCustomer)
tenant.Get("/customer/:id/bets", a.authMiddleware, h.GetTenantCustomerBets)
groupV1.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers) groupV1.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers)
groupV1.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID) groupV1.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID)
groupV1.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer) groupV1.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer)
tenant.Get("/customer/:id/bets", a.authMiddleware, h.GetCustomerBets)
groupV1.Get("/admin", a.authMiddleware, h.GetAllAdmins) groupV1.Get("/admin", a.authMiddleware, h.GetAllAdmins)
groupV1.Get("/admin/:id", a.authMiddleware, h.GetAdminByID) groupV1.Get("/admin/:id", a.authMiddleware, h.GetAdminByID)
groupV1.Post("/admin", a.authMiddleware, h.CreateAdmin) groupV1.Post("/admin", a.authMiddleware, h.CreateAdmin)
groupV1.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin) groupV1.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin)
groupV1.Get("/t-approver", a.authMiddleware, h.GetAllTransactionApprovers)
groupV1.Get("/t-approver/:id", a.authMiddleware, h.GetTransactionApproverByID)
groupV1.Post("/t-approver", a.authMiddleware, h.CreateTransactionApprover)
groupV1.Put("/t-approver/:id", a.authMiddleware, h.UpdateTransactionApprover)
groupV1.Get("/managers", a.authMiddleware, h.GetAllManagers) groupV1.Get("/managers", a.authMiddleware, h.GetAllManagers)
groupV1.Get("/managers/:id", a.authMiddleware, h.GetManagerByID) groupV1.Get("/managers/:id", a.authMiddleware, h.GetManagerByID)
groupV1.Post("/managers", a.authMiddleware, h.CreateManager) groupV1.Post("/managers", a.authMiddleware, h.CreateManager)
@ -200,8 +237,8 @@ func (a *App) initAppRoutes() {
tenant.Get("/odds/upcoming/:upcoming_id", h.GetTenantOddsByUpcomingID) tenant.Get("/odds/upcoming/:upcoming_id", h.GetTenantOddsByUpcomingID)
tenant.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetTenantOddsByMarketID) tenant.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetTenantOddsByMarketID)
groupV1.Get("/events", a.authMiddleware, a.SuperAdminOnly, h.GetAllUpcomingEvents) groupV1.Get("/events", a.authMiddleware, h.GetAllUpcomingEvents)
groupV1.Get("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.GetUpcomingEventByID) groupV1.Get("/events/:id", a.authMiddleware, h.GetUpcomingEventByID)
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved) groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
tenant.Get("/events", h.GetTenantUpcomingEvents) tenant.Get("/events", h.GetTenantUpcomingEvents)
@ -221,6 +258,7 @@ func (a *App) initAppRoutes() {
groupV1.Post("/branch", a.authMiddleware, h.CreateBranch) groupV1.Post("/branch", a.authMiddleware, h.CreateBranch)
groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches) groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches)
groupV1.Get("/branch/:id", a.authMiddleware, h.GetBranchByID) groupV1.Get("/branch/:id", a.authMiddleware, h.GetBranchByID)
groupV1.Post("/branch/:id/return", a.authMiddleware, h.ReturnBranchWallet)
groupV1.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID) groupV1.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID)
groupV1.Put("/branch/:id", a.authMiddleware, h.UpdateBranch) groupV1.Put("/branch/:id", a.authMiddleware, h.UpdateBranch)
groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus) groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
@ -258,15 +296,19 @@ func (a *App) initAppRoutes() {
tenant.Get("/ticket/:id", h.GetTicketByID) tenant.Get("/ticket/:id", h.GetTicketByID)
// Bet Routes // Bet Routes
tenantAuth.Post("/sport/bet", h.CreateBet) tenant.Post("/sport/bet", a.authMiddleware, h.CreateBet)
tenantAuth.Post("/sport/bet/fastcode", h.CreateBetWithFastCode) tenant.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
tenant.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode) tenant.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode)
tenantAuth.Get("/sport/bet", h.GetAllBet) tenant.Get("/sport/bet", a.authMiddleware, h.GetAllTenantBets)
tenantAuth.Get("/sport/bet/:id", h.GetBetByID) tenant.Get("/sport/bet/:id", a.authMiddleware, h.GetTenantBetByID)
tenantAuth.Patch("/sport/bet/:id", h.UpdateCashOut) tenant.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)
tenantAuth.Delete("/sport/bet/:id", h.DeleteBet) tenant.Delete("/sport/bet/:id", a.authMiddleware, h.DeleteTenantBet)
tenantAuth.Post("/sport/random/bet", h.RandomBet) groupV1.Get("/sport/bet", a.authMiddleware, a.SuperAdminOnly, h.GetAllBet)
groupV1.Get("/sport/bet/:id", a.authMiddleware, a.SuperAdminOnly, h.GetBetByID)
groupV1.Delete("/sport/bet/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteBet)
tenant.Post("/sport/random/bet", a.authMiddleware, h.RandomBet)
// Wallet // Wallet
groupV1.Get("/wallet", h.GetAllWallets) groupV1.Get("/wallet", h.GetAllWallets)
@ -373,9 +415,9 @@ func (a *App) initAppRoutes() {
groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey) groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey)
groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList) groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
tenantAuth.Post("/settings", h.SaveCompanySettingList) tenant.Post("/settings", a.authMiddleware, h.SaveCompanySettingList)
tenantAuth.Get("/settings", h.GetCompanySettingList) tenant.Get("/settings", a.authMiddleware, h.GetCompanySettingList)
tenantAuth.Delete("/settings/:key", h.DeleteCompanySetting) tenant.Delete("/settings/:key", a.authMiddleware, h.DeleteCompanySetting)
tenantAuth.Delete("/settings", h.DeleteAllCompanySetting) tenant.Delete("/settings", a.authMiddleware, h.DeleteAllCompanySetting)
} }

View File

@ -56,8 +56,19 @@ restore:
restore_file: restore_file:
@echo "Restoring latest backup..." @echo "Restoring latest backup..."
gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
.PHONY: seed_data
seed_data: seed_data:
cat db/data/seed_data.sql | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
@echo "Waiting for PostgreSQL to be ready..."
@until docker exec fortunebet-backend-postgres-1 pg_isready -U root -d gh; do \
echo "PostgreSQL is not ready yet..."; \
sleep 1; \
done
@for file in db/data/*.sql; do \
echo "Seeding $$file..."; \
cat $$file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; \
done
postgres_log: postgres_log:
docker logs fortunebet-backend-postgres-1 docker logs fortunebet-backend-postgres-1
.PHONY: swagger .PHONY: swagger
@ -69,11 +80,12 @@ logs:
@mkdir -p logs @mkdir -p logs
db-up: | logs db-up: | logs
@mkdir -p logs @mkdir -p logs
@docker compose up -d postgres migrate mongo redis @docker compose up -d postgres migrate mongo redis --wait migrate
@make seed_data
@docker logs fortunebet-backend-postgres-1 > logs/postgres.log 2>&1 & @docker logs fortunebet-backend-postgres-1 > logs/postgres.log 2>&1 &
.PHONY: db-down .PHONY: db-down
db-down: db-down:
@docker compose down @docker compose down -v
@docker volume rm fortunebet-backend_postgres_data @docker volume rm fortunebet-backend_postgres_data
.PHONY: sqlc-gen .PHONY: sqlc-gen
sqlc-gen: sqlc-gen: