CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE, phone_number VARCHAR(20) UNIQUE, role VARCHAR(50) NOT NULL, password BYTEA NOT NULL, email_verified BOOLEAN NOT NULL DEFAULT FALSE, phone_verified BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ, company_id BIGINT, suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended suspended BOOLEAN NOT NULL DEFAULT FALSE, CHECK ( email IS NOT NULL OR phone_number IS NOT NULL ) ); CREATE TABLE refresh_tokens ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, token TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, revoked BOOLEAN DEFAULT FALSE NOT NULL, CONSTRAINT unique_token UNIQUE (token) ); ----- CREATE TABLE otps ( id BIGSERIAL PRIMARY KEY, sent_to VARCHAR(255) NOT NULL, medium VARCHAR(50) NOT NULL, otp_for VARCHAR(50) NOT NULL, otp VARCHAR(10) NOT NULL, used BOOLEAN NOT NULL DEFAULT FALSE, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMPTZ NOT NULL ); CREATE TABLE IF NOT EXISTS bets ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, total_odds REAL NOT NULL, status INT NOT NULL, user_id BIGINT NOT NULL, is_shop_bet BOOLEAN NOT NULL, cashed_out BOOLEAN NOT NULL DEFAULT false, outcomes_hash TEXT NOT NULL, fast_code VARCHAR(10) NOT NULL, processed BOOLEAN DEFAULT FALSE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS tickets ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, total_odds REAL NOT NULL, IP VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE exchange_rates ( id SERIAL PRIMARY KEY, from_currency VARCHAR(3) NOT NULL, to_currency VARCHAR(3) NOT NULL, rate DECIMAL(19, 6) NOT NULL, valid_until TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE (from_currency, to_currency) ); CREATE TABLE IF NOT EXISTS bet_outcomes ( id BIGSERIAL PRIMARY KEY, bet_id BIGINT NOT NULL, sport_id BIGINT NOT NULL, event_id BIGINT NOT null, odd_id BIGINT NOT NULL, home_team_name VARCHAR(255) NOT NULL, away_team_name VARCHAR(255) NOT NULL, market_id BIGINT NOT NULL, market_name VARCHAR(255) NOT NULL, odd REAL NOT NULL, odd_name VARCHAR(255) NOT NULL, odd_header VARCHAR(255) NOT NULL, odd_handicap VARCHAR(255) NOT NULL, status INT NOT NULL DEFAULT 0, expires TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS ticket_outcomes ( id BIGSERIAL PRIMARY KEY, ticket_id BIGINT NOT NULL, event_id BIGINT NOT null, odd_id BIGINT NOT NULL, home_team_name VARCHAR(255) NOT NULL, away_team_name VARCHAR(255) NOT NULL, market_id BIGINT NOT NULL, market_name VARCHAR(255) NOT NULL, odd REAL NOT NULL, odd_name VARCHAR(255) NOT NULL, odd_header VARCHAR(255) NOT NULL, odd_handicap VARCHAR(255) NOT NULL, status INT NOT NULL DEFAULT 0, expires TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS banks ( id BIGSERIAL PRIMARY KEY, slug VARCHAR(255) NOT NULL UNIQUE, swift VARCHAR(20) NOT NULL, name VARCHAR(255) NOT NULL, acct_length INT NOT NULL, country_id INT NOT NULL, is_mobilemoney INT, -- nullable integer (0 or 1) is_active INT NOT NULL, -- 0 or 1 is_rtgs INT NOT NULL, -- 0 or 1 active INT NOT NULL, -- 0 or 1 is_24hrs INT, -- nullable integer (0 or 1) created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, currency VARCHAR(10) NOT NULL, bank_logo TEXT -- URL or base64 string ); CREATE TABLE IF NOT EXISTS wallets ( id BIGSERIAL PRIMARY KEY, balance BIGINT NOT NULL DEFAULT 0, is_withdraw BOOLEAN NOT NULL, is_bettable BOOLEAN NOT NULL, is_transferable BOOLEAN NOT NULL, user_id BIGINT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS customer_wallets ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL, regular_wallet_id BIGINT NOT NULL, static_wallet_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS wallet_transfer ( id BIGSERIAL PRIMARY KEY, amount BIGINT, message VARCHAR(255) NOT NULL, type VARCHAR(255), receiver_wallet_id BIGINT, sender_wallet_id BIGINT, cashier_id BIGINT, verified BOOLEAN DEFAULT false, reference_number VARCHAR(255) NOT NULL, status VARCHAR(255), payment_method VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS shop_transactions ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, branch_id BIGINT NOT NULL, company_id BIGINT NOT NULL, user_id BIGINT NOT NULL, type BIGINT NOT NULL, full_name VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, payment_option BIGINT NOT NULL, bank_code VARCHAR(255), beneficiary_name VARCHAR(255), account_name VARCHAR(255), account_number VARCHAR(255), reference_number VARCHAR(255), approved_by BIGINT, verified BOOLEAN DEFAULT false NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS shop_bets ( id BIGSERIAL PRIMARY KEY, shop_transaction_id BIGINT NOT NULL, cashout_id VARCHAR(255) NOT NULL, cashed_out_by BIGINT, bet_id BIGINT NOT NULL, number_of_outcomes BIGINT NOT NULL, cashed_out BOOLEAN DEFAULT FALSE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(shop_transaction_id), UNIQUE(bet_id), UNIQUE(cashout_id) ); CREATE TABLE IF NOT EXISTS shop_deposits ( id BIGSERIAL PRIMARY KEY, shop_transaction_id BIGINT NOT NULL, customer_id BIGINT NOT NULL, wallet_transfer_id BIGINT, branch_wallet_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(shop_transaction_id) ); CREATE TABLE IF NOT EXISTS branches ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, location TEXT NOT NULL, profit_percent REAL NOT NULL, is_active BOOLEAN NOT NULL DEFAULT false, wallet_id BIGINT NOT NULL, branch_manager_id BIGINT NOT NULL, company_id BIGINT NOT NULL, is_self_owned BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(wallet_id), CONSTRAINT profit_percentage_check CHECK ( profit_percent >= 0 AND profit_percent < 1 ) ); CREATE TABLE IF NOT EXISTS branch_operations ( id BIGSERIAL PRIMARY KEY, operation_id BIGINT NOT NULL, branch_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS branch_cashiers ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, branch_id BIGINT NOT NULL, UNIQUE(user_id, branch_id) ); CREATE TABLE IF NOT EXISTS branch_locations ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE events ( id TEXT PRIMARY KEY, sport_id INT, match_name TEXT, home_team TEXT, away_team TEXT, home_team_id INT, away_team_id INT, home_kit_image TEXT, away_kit_image TEXT, league_id INT, league_name TEXT, league_cc TEXT, start_time TIMESTAMP, score TEXT, match_minute INT, timer_status TEXT, added_time INT, match_period INT, is_live BOOLEAN, status TEXT, fetched_at TIMESTAMP DEFAULT now(), source TEXT DEFAULT 'b365api', is_featured BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE odds ( id SERIAL PRIMARY KEY, event_id TEXT, fi TEXT, market_type TEXT NOT NULL, market_name TEXT, market_category TEXT, market_id TEXT, name TEXT, handicap TEXT, odds_value DOUBLE PRECISION, section TEXT NOT NULL, category TEXT, raw_odds JSONB, fetched_at TIMESTAMP DEFAULT now(), source TEXT DEFAULT 'b365api', is_active BOOLEAN DEFAULT true, UNIQUE (market_id, name, handicap), UNIQUE (event_id, market_id, name, handicap), UNIQUE (event_id, market_id) ); CREATE TABLE companies ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, admin_id BIGINT NOT NULL, wallet_id BIGINT NOT NULL, deducted_percentage REAL NOT NULL, is_active BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT deducted_percentage_check CHECK ( deducted_percentage >= 0 AND deducted_percentage < 1 ) ); CREATE TABLE leagues ( id BIGINT PRIMARY KEY, name TEXT NOT NULL, img TEXT, country_code TEXT, bet365_id INT, sport_id INT NOT NULL, is_active BOOLEAN DEFAULT true, is_featured BOOLEAN DEFAULT false ); CREATE TABLE teams ( id TEXT PRIMARY KEY, team_name TEXT NOT NULL, country TEXT, bet365_id INT, logo_url TEXT ); CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE bonus ( id BIGSERIAL PRIMARY KEY, multiplier REAL NOT NULL, balance_cap BIGINT NOT NULL DEFAULT 0 ); CREATE TABLE flags ( id BIGSERIAL PRIMARY KEY, bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE, odd_id BIGINT REFERENCES odds(id), reason TEXT, flagged_at TIMESTAMP DEFAULT NOW(), resolved BOOLEAN DEFAULT FALSE, -- either bet or odd is flagged (not at the same time) CHECK ( (bet_id IS NOT NULL AND odd_id IS NULL) OR (bet_id IS NULL AND odd_id IS NOT NULL) ) ); -- Views CREATE VIEW companies_details AS SELECT companies.*, wallets.balance, wallets.is_active as wallet_is_active, users.first_name AS admin_first_name, users.last_name AS admin_last_name, users.phone_number AS admin_phone_number FROM companies JOIN wallets ON wallets.id = companies.wallet_id JOIN users ON users.id = companies.admin_id; ; CREATE VIEW branch_details AS SELECT branches.*, CONCAT(users.first_name, ' ', users.last_name) AS manager_name, users.phone_number AS manager_phone_number, wallets.balance, wallets.is_active AS wallet_is_active FROM branches LEFT JOIN users ON branches.branch_manager_id = users.id LEFT JOIN wallets ON wallets.id = branches.wallet_id; CREATE TABLE IF NOT EXISTS supported_operations ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL ); CREATE VIEW bet_with_outcomes AS SELECT bets.*, CONCAT(users.first_name, ' ', users.last_name) AS full_name, users.phone_number, JSON_AGG(bet_outcomes.*) AS outcomes FROM bets LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id LEFT JOIN users ON bets.user_id = users.id GROUP BY bets.id, users.first_name, users.last_name, users.phone_number; CREATE VIEW ticket_with_outcomes AS SELECT tickets.*, JSON_AGG(ticket_outcomes.*) AS outcomes FROM tickets LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id GROUP BY tickets.id; CREATE VIEW customer_wallet_details AS SELECT cw.id, cw.customer_id, rw.id AS regular_id, rw.balance AS regular_balance, sw.id AS static_id, sw.balance AS static_balance, rw.is_active as regular_is_active, sw.is_active as static_is_active, rw.updated_at as regular_updated_at, sw.updated_at as static_updated_at, cw.created_at, users.first_name, users.last_name, users.phone_number FROM customer_wallets cw JOIN wallets rw ON cw.regular_wallet_id = rw.id JOIN wallets sw ON cw.static_wallet_id = sw.id JOIN users ON users.id = cw.customer_id; CREATE VIEW wallet_transfer_details AS SELECT wt.*, users.first_name, users.last_name, users.phone_number FROM wallet_transfer wt LEFT JOIN users ON users.id = wt.cashier_id; CREATE VIEW shop_transaction_detail AS SELECT st.*, cr.first_name AS creator_first_name, cr.last_name AS creator_last_name, cr.phone_number AS creator_phone_number, ap.first_name AS approver_first_name, ap.last_name AS approver_last_name, ap.phone_number AS approver_phone_number, branches.name AS branch_name, branches.location AS branch_location FROM shop_transactions st LEFT JOIN users cr ON cr.id = st.user_id LEFT JOIN users ap ON ap.id = st.approved_by LEFT JOIN branches ON branches.id = st.branch_id; CREATE VIEW shop_bet_detail AS SELECT sb.*, st.full_name AS customer_full_name, st.phone_number AS customer_phone_number, st.branch_id, st.company_id, st.amount, st.verified AS transaction_verified, bets.status, bets.total_odds, JSON_AGG(bet_outcomes.*) AS outcomes FROM shop_bets AS sb JOIN shop_transactions st ON st.id = sb.shop_transaction_id JOIN bets ON bets.id = sb.bet_id LEFT JOIN bet_outcomes ON bet_outcomes.bet_id = sb.bet_id GROUP BY sb.id, st.full_name, st.phone_number, st.branch_id, st.company_id, st.amount, st.verified, bets.status, bets.total_odds; CREATE VIEW shop_deposit_detail AS SELECT sd.*, st.full_name, st.phone_number, st.branch_id, st.company_id, st.amount, st.verified AS transaction_verified FROM shop_deposits AS sd JOIN shop_transactions st ON st.id = sd.shop_transaction_id; -- Foreign Keys ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email), ADD CONSTRAINT unique_phone_number UNIQUE (phone_number); ALTER TABLE refresh_tokens ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE bets ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE wallets ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id), ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB'; ALTER TABLE customer_wallets ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id); ALTER TABLE wallet_transfer ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); ALTER TABLE shop_transactions ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users(id); ALTER TABLE shop_bets ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets(id); ALTER TABLE shop_deposits ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id); ALTER TABLE branches ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id), ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations(key); ALTER TABLE branch_operations ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ALTER TABLE branch_cashiers ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; ALTER TABLE companies ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;