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

@ -21,14 +21,18 @@ CREATE TABLE IF NOT EXISTS users (
UNIQUE (email, company_id),
UNIQUE (phone_number, company_id)
);
CREATE TABLE IF NOT EXISTS virtual_game_providers (
id BIGSERIAL PRIMARY KEY,
provider_id VARCHAR(100) UNIQUE NOT NULL, -- providerId from Veli Games
provider_name VARCHAR(255) NOT NULL, -- providerName
logo_dark TEXT, -- logoForDark (URL)
logo_light TEXT, -- logoForLight (URL)
enabled BOOLEAN NOT NULL DEFAULT TRUE, -- allow enabling/disabling providers
provider_id VARCHAR(100) UNIQUE NOT NULL,
-- providerId from Veli Games
provider_name VARCHAR(255) NOT NULL,
-- providerName
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,
updated_at TIMESTAMPTZ
);
@ -40,7 +44,14 @@ CREATE TABLE IF NOT EXISTS wallets (
is_bettable BOOLEAN NOT NULL,
is_transferable BOOLEAN NOT NULL,
user_id BIGINT NOT NULL,
type VARCHAR(255) NOT NULL,
type TEXT NOT NULL CHECK (
type IN (
'regular_wallet',
'static_wallet',
'branch_wallet',
'company_wallet'
)
),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
@ -265,10 +276,7 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
branch_id BIGINT NOT NULL,
UNIQUE (user_id, branch_id)
);
CREATE TABLE IF NOT EXISTS branch_locations (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS branch_locations (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE events (
id TEXT PRIMARY KEY,
sport_id INT NOT NULL,
@ -291,13 +299,7 @@ CREATE TABLE events (
status TEXT NOT NULL,
fetched_at TIMESTAMP DEFAULT now (),
source TEXT NOT NULL DEFAULT 'b365api' CHECK (
source IN (
'b365api',
'bfair',
'1xbet',
'bwin',
'enetpulse'
)
source IN ('b365api', 'bfair', '1xbet', 'bwin', 'enetpulse')
),
default_is_active BOOLEAN NOT NULL DEFAULT true,
default_is_featured BOOLEAN NOT NULL DEFAULT false,

View File

@ -18,17 +18,19 @@ CREATE TABLE IF NOT EXISTS referral_settings (
);
CREATE TABLE IF NOT EXISTS referrals (
id BIGSERIAL PRIMARY KEY,
company_id BIGINT NOT NULL REFERENCES companies (id) ON
DELETE CASCADE,
referral_code VARCHAR(10) NOT NULL UNIQUE,
referrer_id VARCHAR(255) NOT NULL,
referred_id VARCHAR(255) UNIQUE,
referrer_id BIGINT NOT NULL REFERENCES users (id),
referred_id BIGINT UNIQUE REFERENCES users (id),
status ReferralStatus NOT NULL DEFAULT 'PENDING',
reward_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,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL,
-- FOREIGN KEY (referrer_id) REFERENCES users (id),
expires_at TIMESTAMPTZ NOT NULL -- FOREIGN KEY (referrer_id) REFERENCES users (id),
-- FOREIGN KEY (referred_id) REFERENCES users (id),
,
CONSTRAINT reward_amount_positive CHECK (reward_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
ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE,
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
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,

View File

@ -1,11 +1,11 @@
-- 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
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
-- UPDATE
-- SET value = EXCLUDED.value;

View File

@ -1,75 +1,75 @@
-- 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;
-- -- 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;

View File

@ -1,218 +1,220 @@
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('password123', 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,
admin_id,
wallet_id,
deducted_percentage,
is_active,
created_at,
updated_at
)
VALUES (
1,
'Test Company',
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
);
-- 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,
-- 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
-- ),
-- (
-- 5,
-- 'Test',
-- 'Veli',
-- 'test.veli@example.com',
-- NULL,
-- crypt('password@123', gen_salt('bf'))::bytea,
-- 'customer',
-- TRUE,
-- FALSE,
-- CURRENT_TIMESTAMP,
-- CURRENT_TIMESTAMP,
-- FALSE,
-- 1
-- );
-- -- 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_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
-- );
-- -- 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
-- );

View File

@ -2,50 +2,55 @@
INSERT INTO referrals (
referral_code,
referrer_id,
company_id,
status,
reward_amount,
expires_at
) VALUES (
$1, $2, $3, $4, $5
) RETURNING *;
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *;
-- name: GetReferralByCode :one
SELECT * FROM referrals
SELECT *
FROM referrals
WHERE referral_code = $1;
-- name: UpdateReferral :one
UPDATE referrals
SET
referred_id = $2,
SET referred_id = $2,
status = $3,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *;
-- name: UpdateReferralCode :exec
UPDATE users
SET
referral_code = $2,
SET referral_code = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1;
-- name: GetReferralStats :one
SELECT
COUNT(*) as total_referrals,
COUNT(CASE WHEN status = 'COMPLETED' THEN 1 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
SELECT COUNT(*) AS total_referrals,
COUNT(
CASE
WHEN status = 'COMPLETED' THEN 1
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
WHERE referrer_id = $1;
WHERE referrer_id = $1
AND company_id = $2;
-- name: GetReferralSettings :one
SELECT * FROM referral_settings
SELECT *
FROM referral_settings
LIMIT 1;
-- name: UpdateReferralSettings :one
UPDATE referral_settings
SET
referral_reward_amount = $2,
SET referral_reward_amount = $2,
cashback_percentage = $3,
bet_referral_bonus_percentage = $4,
max_referrals = $5,
@ -54,7 +59,6 @@ SET
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *;
-- name: CreateReferralSettings :one
INSERT INTO referral_settings (
referral_reward_amount,
@ -63,15 +67,21 @@ INSERT INTO referral_settings (
bet_referral_bonus_percentage,
expires_after_days,
updated_by
) VALUES (
$1, $2, $3, $4, $5, $6
) RETURNING *;
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *;
-- name: GetReferralByReferredID :one
SELECT * FROM referrals WHERE referred_id = $1 LIMIT 1;
SELECT *
FROM referrals
WHERE referred_id = $1
LIMIT 1;
-- 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
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 (
email = $2
OR phone_number = $3
AND company_id = $4
AND company_id = $5
);
-- name: GetAdminByCompanyID :one
SELECT users.*

View File

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

View File

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

View File

@ -587,7 +587,7 @@ SET password = $1,
WHERE (
email = $2
OR phone_number = $3
AND company_id = $4
AND company_id = $5
)
`
@ -596,6 +596,7 @@ type UpdatePasswordParams struct {
Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
CompanyID pgtype.Int8 `json:"company_id"`
}
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.PhoneNumber,
arg.UpdatedAt,
arg.CompanyID,
)
return err
}

View File

@ -10,3 +10,10 @@ type RefreshToken struct {
CreatedAt time.Time
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 {
ID int64
ReferralCode string
ReferrerID string
ReferredID *string
ReferrerID int64
CompanyID int64
ReferredID *int64
Status ReferralStatus
RewardAmount float64
CashbackAmount float64

View File

@ -8,11 +8,12 @@ const (
RoleBranchManager Role = "branch_manager"
RoleCustomer Role = "customer"
RoleCashier Role = "cashier"
RoleTransactionApprover Role = "transaction_approver"
)
func (r Role) IsValid() bool {
switch r {
case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier:
case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier, RoleTransactionApprover:
return true
default:
return false

View File

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

View File

@ -76,7 +76,8 @@ type CreateCustomerWallet struct {
type WalletType string
const (
CustomerWalletType WalletType = "customer_wallet"
RegularWalletType WalletType = "regular_wallet"
StaticWalletType WalletType = "static_wallet"
BranchWalletType WalletType = "branch_wallet"
CompanyWalletType WalletType = "company_wallet"
)
@ -92,18 +93,18 @@ const (
)
type DirectDeposit struct {
ID int64
CustomerID int64
WalletID int64
Wallet Wallet // Joined data
Amount Currency
BankReference string
SenderAccount string
Status DirectDepositStatus
CreatedAt time.Time
VerifiedBy *int64 // Nullable
VerificationNotes string
VerifiedAt *time.Time // Nullable
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
WalletID int64 `json:"wallet_id"`
Wallet Wallet `json:"wallet"`
Amount Currency `json:"amount"`
BankReference string `json:"bank_reference"`
SenderAccount string `json:"sender_account"`
Status DirectDepositStatus `json:"status"`
CreatedAt time.Time `json:"created_at"`
VerifiedBy *int64 `json:"verified_by"`
VerificationNotes string `json:"verification_notes"`
VerifiedAt *time.Time `json:"verified_at"`
}
type CreateDirectDeposit struct {

View File

@ -16,13 +16,13 @@ type ReferralRepository interface {
CreateReferral(ctx context.Context, referral *domain.Referral) error
GetReferralByCode(ctx context.Context, code string) (*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)
UpdateSettings(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
GetReferralCountByID(ctx context.Context, referrerID string) (int64, error)
GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error)
GetReferralByReferredID(ctx context.Context, referredID int64) (*domain.Referral, error)
GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error)
GetActiveReferralByReferrerID(ctx context.Context, referrerID int64) (*domain.Referral, 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),
RewardAmount: rewardAmount,
ExpiresAt: pgtype.Timestamptz{Time: referral.ExpiresAt, Valid: true},
CompanyID: referral.CompanyID,
}
_, 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 {
var referredID pgtype.Text
var referredID pgtype.Int8
if referral.ReferredID != nil {
referredID = pgtype.Text{String: *referral.ReferredID, Valid: true}
referredID = pgtype.Int8{Int64: *referral.ReferredID, Valid: true}
}
params := dbgen.UpdateReferralParams{
@ -91,8 +92,11 @@ func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Refe
return err
}
func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) {
stats, err := r.store.queries.GetReferralStats(ctx, userID)
func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error) {
stats, err := r.store.queries.GetReferralStats(ctx, dbgen.GetReferralStatsParams{
ReferrerID: userID,
CompanyID: companyID,
})
if err != nil {
return nil, err
}
@ -175,8 +179,8 @@ func (r *ReferralRepo) CreateSettings(ctx context.Context, settings *domain.Refe
return err
}
func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) {
dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Text{String: referredID, Valid: true})
func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID int64) (*domain.Referral, error) {
dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Int8{Int64: referredID, Valid: true})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@ -186,7 +190,7 @@ func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID s
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)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@ -198,7 +202,7 @@ func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID stri
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)
if err != nil {
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 {
var referredID *string
var referredID *int64
if dbRef.ReferredID.Valid {
referredID = &dbRef.ReferredID.String
referredID = &dbRef.ReferredID.Int64
}
rewardAmount := 0.0

View File

@ -428,7 +428,7 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string, companyID d
}, 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{
ID: usedOtpId,
UsedAt: pgtype.Timestamptz{
@ -449,6 +449,10 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password
String: identifier,
Valid: true,
},
CompanyID: pgtype.Int8{
Int64: companyId,
Valid: true,
},
})
if err != nil {
return err

View File

@ -8,13 +8,13 @@ import (
type ReferralStore interface {
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
ProcessDepositBonus(ctx context.Context, userID string, amount float64) error
GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error)
ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) 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
UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error
GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error)
GetReferralCountByID(ctx context.Context, referrerID string) (int64, error)
ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error
GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error)
}

View File

@ -52,11 +52,11 @@ func (s *Service) GenerateReferralCode() (string, error) {
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)
// 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 {
s.logger.Error("Failed to check if user alredy has active referral code", "error", err)
return err
@ -73,7 +73,7 @@ func (s *Service) CreateReferral(ctx context.Context, userID int64) error {
}
// check referral count limit
referralCount, err := s.GetReferralCountByID(ctx, fmt.Sprintf("%d", userID))
referralCount, err := s.GetReferralCountByID(ctx, userID)
if err != nil {
s.logger.Error("Failed to get referral count", "userID", userID, "error", 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{
ReferralCode: code,
ReferrerID: fmt.Sprintf("%d", userID),
ReferrerID: userID,
Status: domain.ReferralPending,
RewardAmount: rewardAmount,
ExpiresAt: expireDuration,
CompanyID: companyID,
}); err != nil {
return err
}
@ -138,7 +139,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
return ErrInvalidReferralSignup
}
referral.ReferredID = &referredPhone
referral.ReferredID = &user.ID
referral.Status = domain.ReferralCompleted
referral.UpdatedAt = time.Now()
@ -147,13 +148,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
return err
}
referrerId, err := strconv.Atoi(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))
wallets, err := s.store.GetCustomerWallet(ctx, referral.ReferrerID)
if err != nil {
s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err)
return err
@ -161,7 +156,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID,
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 {
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
}
func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error {
s.logger.Info("Processing bet referral", "userPhone", userPhone, "betAmount", betAmount)
func (s *Service) ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error {
s.logger.Info("Processing bet referral", "userID", userId, "betAmount", betAmount)
settings, err := s.repo.GetSettings(ctx)
if err != nil {
@ -221,29 +216,24 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
return err
}
referral, err := s.repo.GetReferralByReferredID(ctx, userPhone)
referral, err := s.repo.GetReferralByReferredID(ctx, userId)
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
}
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
}
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 {
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
}
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")
}
@ -260,24 +250,24 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
domain.TRANSFER_DIRECT, domain.PaymentDetails{},
fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount))
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
}
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
}
func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) {
s.logger.Info("Fetching referral stats", "userPhone", userPhone)
func (s *Service) GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error) {
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 {
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
}
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
}
@ -328,7 +318,7 @@ func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSett
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)
if err != nil {
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)
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)
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)
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
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID, resetReq.CompanyID)
if err != nil {
return err
}

View File

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

View File

@ -196,11 +196,13 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
Valid: true,
}
}
companyFilter := int64(c.QueryInt("company_id"))
filter := domain.UserFilter{
Role: string(domain.RoleAdmin),
CompanyID: domain.ValidInt64{
Value: int64(c.QueryInt("company_id")),
Valid: false,
Value: companyFilter,
Valid: companyFilter != 0,
},
Page: domain.ValidInt{
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 ")
}
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 {
h.mongoLoggerSvc.Error("Failed to create access token",
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
// @Router /api/v1/{tenant_slug}/sport/bet [get]
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
// companyID := c.Locals("company_id").(domain.ValidInt64)
// branchID := c.Locals("branch_id").(domain.ValidInt64)
var isShopBet domain.ValidBool
isShopBetQuery := c.Query("is_shop")
if isShopBetQuery != "" && 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)
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")
}
role := c.Locals("role").(domain.Role)
@ -485,8 +586,54 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
// @Success 200 {object} domain.BetRes
// @Failure 400 {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 {
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)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
@ -696,8 +843,52 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
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)
if !companyID.Valid {
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)
}
// GetBranchByID godoc
// @Summary Gets 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)
}
// 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
// @Summary Gets 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"`
SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"`
CompanyID int64 `json:"company_id"`
CompanyName string `json:"company_name"`
}
// GetAllCustomers godoc
@ -156,6 +158,30 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) 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{
ID: customer.ID,
FirstName: customer.FirstName,
@ -170,6 +196,176 @@ func (h *Handler) GetAllCustomers(c *fiber.Ctx) error {
SuspendedAt: customer.SuspendedAt,
Suspended: customer.Suspended,
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
}
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{
ID: user.ID,
FirstName: user.FirstName,
@ -236,16 +455,226 @@ func (h *Handler) GetCustomerByID(c *fiber.Ctx) error {
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)
}
// 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 {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Suspended bool `json:"suspended" example:"false"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
}
// 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)
}
// 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 {
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.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")
}
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",
zap.Int64("userID", userID),
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 {
var req domain.ReferralSettingsReq
if err := c.BodyParser(&req); err != nil {
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)
}
// 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
// @Summary Get referral statistics
// @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 500 {object} response.APIResponse
// @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 {
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",
@ -124,7 +186,17 @@ func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
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 {
h.mongoLoggerSvc.Error("Failed to get referral stats",
zap.Int64("userID", userID),
@ -227,33 +299,9 @@ func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
// h.logger.Error("Invalid user ID in context")
// 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())
if err != nil {
h.mongoLoggerSvc.Error("Failed to get referral settings",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),

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

@ -149,7 +149,7 @@ type RegisterUserReq struct {
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
ReferralCode string `json:"referral_code" example:"ABC123"`
}
// RegisterUser godoc
@ -193,7 +193,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Otp: req.Otp,
ReferralCode: req.ReferalCode,
ReferralCode: req.ReferralCode,
OtpMedium: domain.OtpMediumEmail,
CompanyID: companyID,
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())
}
if req.ReferalCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode, companyID.Value)
if req.ReferralCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferralCode, companyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Failed to process referral during registration",
zap.String("phone", req.PhoneNumber),
zap.String("code", req.ReferalCode),
zap.String("code", req.ReferralCode),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
@ -293,8 +293,70 @@ type ResetCodeReq struct {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
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)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
@ -367,7 +429,7 @@ type ResetPasswordReq struct {
// @Success 200 {object} response.APIResponse
// @Failure 400 {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 {
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)
}
// 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 {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
@ -503,6 +635,7 @@ func (h *Handler) CustomerProfile(c *fiber.Ctx) error {
lastLogin = &user.CreatedAt
}
res := CustomerProfileRes{
ID: user.ID,
FirstName: user.FirstName,

View File

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

View File

@ -2,6 +2,7 @@ package httpserver
import (
"errors"
"fmt"
"strings"
"time"
@ -56,6 +57,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
zap.Error(err),
)
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("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)
var branchID domain.ValidInt64
@ -198,6 +203,7 @@ func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
zap.String("ip_address", ip),
zap.String("user_agent", userAgent),
zap.Time("timestamp", time.Now()),
zap.Error(err),
)
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 {
if tokenCID, ok := c.Locals("company_id").(domain.ValidInt64); ok && tokenCID.Valid {
return c.Next()
}
tenantSlug := c.Params("tenant_slug")
if tenantSlug == "" {
a.mongoLoggerSvc.Info("blank tenant param",
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "tenant is required for this route")
}
companyID, err := a.companySvc.GetCompanyIDBySlug(c.Context(), tenantSlug)
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")
}
@ -242,3 +251,35 @@ func (a *App) TenantMiddleware(c *fiber.Ctx) error {
})
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,17 +62,42 @@ func (a *App) initAppRoutes() {
})
})
a.fiber.Get("/routes", func(c *fiber.Ctx) error {
return c.JSON(a.fiber.Stack()) // prints all registered routes
})
// Groups
groupV1 := a.fiber.Group("/api/v1")
tenant := groupV1.Group("/:tenant_slug", a.TenantMiddleware)
tenantAuth := groupV1.Group("/:tenant_slug", a.authMiddleware, a.TenantMiddleware)
tenant := groupV1.Group("/tenant/:tenant_slug", 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
groupV1.Post("/direct_deposit", a.authMiddleware, h.InitiateDirectDeposit)
groupV1.Post("/direct_deposit/verify", a.authMiddleware, h.VerifyDirectDeposit)
groupV1.Get("/direct_deposit/pending", a.authMiddleware, h.GetPendingDirectDeposits)
// Swagger
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.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
// User Routes
tenant.Post("/user/resetPassword", h.ResetPassword)
tenant.Post("/user/sendResetCode", h.SendResetCode)
groupV1.Post("/user/resetPassword", h.ResetPassword)
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/sendRegisterCode", h.SendRegisterCode)
tenant.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
tenantAuth.Get("/user/customer-profile", h.CustomerProfile)
tenantAuth.Get("/user/admin-profile", h.AdminProfile)
tenantAuth.Get("/user/bets", h.GetBetByUserID)
tenant.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile)
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.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
tenantAuth.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
tenantAuth.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
tenant.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
tenant.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
// Referral Routes
groupV1.Post("/referral/create", a.authMiddleware, h.CreateReferralCode)
groupV1.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
tenant.Post("/referral/create", a.authMiddleware, h.CreateReferralCode)
tenant.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
groupV1.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings)
groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings)
groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
@ -177,15 +203,26 @@ func (a *App) initAppRoutes() {
groupV1.Post("/cashiers", a.authMiddleware, h.CreateCashier)
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/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID)
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/:id", a.authMiddleware, h.GetAdminByID)
groupV1.Post("/admin", a.authMiddleware, h.CreateAdmin)
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/:id", a.authMiddleware, h.GetManagerByID)
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/market/:market_id", h.GetTenantOddsByMarketID)
groupV1.Get("/events", a.authMiddleware, a.SuperAdminOnly, h.GetAllUpcomingEvents)
groupV1.Get("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.GetUpcomingEventByID)
groupV1.Get("/events", a.authMiddleware, h.GetAllUpcomingEvents)
groupV1.Get("/events/:id", a.authMiddleware, h.GetUpcomingEventByID)
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
tenant.Get("/events", h.GetTenantUpcomingEvents)
@ -221,6 +258,7 @@ func (a *App) initAppRoutes() {
groupV1.Post("/branch", a.authMiddleware, h.CreateBranch)
groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches)
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.Put("/branch/:id", a.authMiddleware, h.UpdateBranch)
groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
@ -258,15 +296,19 @@ func (a *App) initAppRoutes() {
tenant.Get("/ticket/:id", h.GetTicketByID)
// Bet Routes
tenantAuth.Post("/sport/bet", h.CreateBet)
tenantAuth.Post("/sport/bet/fastcode", h.CreateBetWithFastCode)
tenant.Post("/sport/bet", a.authMiddleware, h.CreateBet)
tenant.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
tenant.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode)
tenantAuth.Get("/sport/bet", h.GetAllBet)
tenantAuth.Get("/sport/bet/:id", h.GetBetByID)
tenantAuth.Patch("/sport/bet/:id", h.UpdateCashOut)
tenantAuth.Delete("/sport/bet/:id", h.DeleteBet)
tenant.Get("/sport/bet", a.authMiddleware, h.GetAllTenantBets)
tenant.Get("/sport/bet/:id", a.authMiddleware, h.GetTenantBetByID)
tenant.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)
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
groupV1.Get("/wallet", h.GetAllWallets)
@ -373,9 +415,9 @@ func (a *App) initAppRoutes() {
groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey)
groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
tenantAuth.Post("/settings", h.SaveCompanySettingList)
tenantAuth.Get("/settings", h.GetCompanySettingList)
tenantAuth.Delete("/settings/:key", h.DeleteCompanySetting)
tenantAuth.Delete("/settings", h.DeleteAllCompanySetting)
tenant.Post("/settings", a.authMiddleware, h.SaveCompanySettingList)
tenant.Get("/settings", a.authMiddleware, h.GetCompanySettingList)
tenant.Delete("/settings/:key", a.authMiddleware, h.DeleteCompanySetting)
tenant.Delete("/settings", a.authMiddleware, h.DeleteAllCompanySetting)
}

View File

@ -56,8 +56,19 @@ restore:
restore_file:
@echo "Restoring latest backup..."
gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
.PHONY: 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:
docker logs fortunebet-backend-postgres-1
.PHONY: swagger
@ -69,11 +80,12 @@ logs:
@mkdir -p logs
db-up: | 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 &
.PHONY: db-down
db-down:
@docker compose down
@docker compose down -v
@docker volume rm fortunebet-backend_postgres_data
.PHONY: sqlc-gen
sqlc-gen: