diff --git a/.gitignore b/.gitignore index c29bfa6..660eeeb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ coverage .env tmp build +*.log +db.sql \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f72d738 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Cashout" + ] +} \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 28648bd..943f67c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,10 +1,13 @@ package main import ( + // "context" "fmt" "log/slog" "os" + "github.com/go-playground/validator/v10" + "github.com/SamuelTariku/FortuneBet-Backend/internal/config" customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger" mockemail "github.com/SamuelTariku/FortuneBet-Backend/internal/mocks/mock_email" @@ -12,7 +15,11 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" @@ -22,7 +29,6 @@ import ( httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" - "github.com/go-playground/validator/v10" ) // @title FortuneBet API @@ -41,28 +47,35 @@ import ( func main() { cfg, err := config.NewConfig() if err != nil { - slog.Error(err.Error()) + slog.Error(" Config error:", "err", err) os.Exit(1) } db, _, err := repository.OpenDB(cfg.DbUrl) if err != nil { - fmt.Print("db", err) + fmt.Println(" Database error:", err) os.Exit(1) } + logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel) store := repository.NewStore(db) v := customvalidator.NewCustomValidator(validator.New()) authSvc := authentication.NewService(store, store, cfg.RefreshExpiry) mockSms := mocksms.NewMockSMS() - mockemail := mockemail.NewMockEmail() + mockEmail := mockemail.NewMockEmail() + + userSvc := user.NewService(store, store, mockSms, mockEmail) + + eventSvc := event.New(cfg.Bet365Token, store) + oddsSvc := odds.New(cfg.Bet365Token, store) - userSvc := user.NewService(store, store, mockSms, mockemail) ticketSvc := ticket.NewService(store) betSvc := bet.NewService(store) - walletSvc := wallet.NewService(store) + walletSvc := wallet.NewService(store, store) transactionSvc := transaction.NewService(store) + branchSvc := branch.NewService(store) + companySvc := company.NewService(store) notificationRepo := repository.NewNotificationRepository(store) referalRepo := repository.NewReferralRepository(store) @@ -72,15 +85,17 @@ func main() { referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger) virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) + httpserver.StartDataFetchingCrons(eventSvc, oddsSvc) + app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{ JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, - }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, referalSvc, virtualGameSvc) + }, userSvc, + ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc) logger.Info("Starting server", "port", cfg.Port) if err := app.Run(); err != nil { logger.Error("Failed to start server", "error", err) os.Exit(1) } - } diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index e4d5b9a..82d488d 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -1,22 +1,18 @@ -- Drop tables that depend on service_type_setting DROP TABLE IF EXISTS service_type_setting; - -- Drop product-related tables and types DROP TABLE IF EXISTS product; DROP TYPE IF EXISTS tier_group; - -- Drop onboarding-related tables and types DROP TABLE IF EXISTS verification_key; DROP TABLE IF EXISTS onboarding_user; DROP TYPE IF EXISTS verification_status; DROP TYPE IF EXISTS onboarding_status; - -- Drop staff-related tables and types DROP TABLE IF EXISTS staff_session; DROP TABLE IF EXISTS user_agent; DROP TABLE IF EXISTS staff; DROP TYPE IF EXISTS password_status; - -- Drop mobile app-related tables and types DROP TABLE IF EXISTS user_devices; DROP TABLE IF EXISTS user_session; @@ -25,17 +21,14 @@ DROP TABLE IF EXISTS users; DROP TYPE IF EXISTS device_type; DROP TYPE IF EXISTS registeration_type; DROP TYPE IF EXISTS customer_group; - -- Drop linked accounts and beneficiary tables and types DROP TABLE IF EXISTS beneficiary; DROP TYPE IF EXISTS fund_destination; - -- Drop maker checker-related tables and types DROP TABLE IF EXISTS workflow; DROP TYPE IF EXISTS approval_status; DROP TYPE IF EXISTS action_type; DROP TYPE IF EXISTS context_type; - -- Drop authorization-related tables and types DROP TRIGGER IF EXISTS enforce_unique_array ON policy; DROP FUNCTION IF EXISTS check_unique_array; @@ -43,11 +36,9 @@ DROP TABLE IF EXISTS policy; DROP TABLE IF EXISTS roles; DROP TYPE IF EXISTS policy_action; DROP TYPE IF EXISTS policy_object; - -- Drop bank-related tables and types DROP TABLE IF EXISTS bank; DROP TABLE IF EXISTS flagged_users; - -- Drop transaction-related tables and types DROP TABLE IF EXISTS transaction_daily; DROP TABLE IF EXISTS system_limits; @@ -57,27 +48,31 @@ DROP TYPE IF EXISTS service_type; DROP TYPE IF EXISTS channel; DROP TYPE IF EXISTS transaction_category; DROP TYPE IF EXISTS registration_type; - -- Drop branches and related tables DROP TABLE IF EXISTS branches; DROP TABLE IF EXISTS cities; DROP TABLE IF EXISTS districts; DROP TABLE IF EXISTS regions; - -- Drop activity logs DROP TABLE IF EXISTS activity; - -- Drop ussd account and related enums DROP TABLE IF EXISTS ussd_account; DROP TYPE IF EXISTS ua_pin_status; DROP TYPE IF EXISTS ua_status; DROP TYPE IF EXISTS ua_registaration_type; - -- Drop FortuneBet DROP TABLE IF EXISTS tickets; +DROP TABLE IF EXISTS ticket_outcomes; DROP TABLE IF EXISTS bets; +DROP TABLE IF EXISTS bet_outcomes; DROP TABLE IF EXISTS wallets; +DROP TABLE IF EXISTS customer_wallets; DROP TABLE IF EXISTS wallet_transfer; DROP TABLE IF EXISTS transactions; -DROP TABLE IF EXISTS customer_wallets; - +DROP TABLE IF EXISTS branches; +DROP TABLE IF EXISTS companies; +DROP TABLE IF EXISTS supported_operations; +DROP TABLE IF EXISTS refresh_tokens; +DROP TABLE IF EXISTS otps; +DROP TABLE IF EXISTS odds; +DROP TABLE IF EXISTS events; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 622b60a..111aa73 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -2,31 +2,34 @@ 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 , + 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 , + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ, -- 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) + 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, + 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, +CREATE TABLE otps ( + id BIGSERIAL PRIMARY KEY, sent_to VARCHAR(255) NOT NULL, medium VARCHAR(50) NOT NULL, otp_for VARCHAR(50) NOT NULL, @@ -36,55 +39,87 @@ CREATE TABLE refresh_tokens ( 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, + id BIGSERIAL PRIMARY KEY, + amount BIGINT NOT NULL, + total_odds REAL NOT NULL, status INT NOT NULL, full_name VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, branch_id BIGINT, user_id BIGINT, - cashed_out BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + cashed_out BOOLEAN DEFAULT FALSE NOT NULL, + cashout_id VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_shop_bet BOOLEAN NOT NULL, - CHECK (user_id IS NOT NULL OR branch_id IS NOT NULL) + CHECK ( + user_id IS NOT NULL + OR branch_id IS NOT NULL + ) ); - CREATE TABLE IF NOT EXISTS tickets ( - id BIGSERIAL PRIMARY KEY, - amount BIGINT NULL, - total_odds REAL NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + id BIGSERIAL PRIMARY KEY, + amount BIGINT NOT NULL, + total_odds REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); - --- CREATE TABLE IF NOT EXISTS bet_outcomes ( --- id BIGSERIAL PRIMARY KEY, --- bet_id BIGINT NOT NULL, --- outcome_id BIGINT NOT NULL, --- ); - --- CREATE TABLE IF NOT EXISTS ticket_outcomes ( --- id BIGSERIAL PRIMARY KEY, --- ticket_id BIGINT NOT NULL, --- outcome_id BIGINT NOT NULL, --- ); - +CREATE TABLE IF NOT EXISTS bet_outcomes ( + id BIGSERIAL PRIMARY KEY, + bet_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 VIEW bet_with_outcomes AS +SELECT bets.*, + JSON_AGG(bet_outcomes.*) AS outcomes +FROM bets + LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id +GROUP BY bets.id; +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 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, @@ -95,23 +130,25 @@ CREATE TABLE IF NOT EXISTS customer_wallets ( updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE (customer_id, company_id) ); - CREATE TABLE IF NOT EXISTS wallet_transfer ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, - wallet_transfer VARCHAR(255) NOT NULL, - wallet_id BIGINT NOT NULL, + type VARCHAR(255) NOT NULL, + receiver_wallet_id BIGINT NOT NULL, + sender_wallet_id BIGINT, + cashier_id BIGINT, verified BOOLEAN NOT NULL DEFAULT false, + payment_method VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); - CREATE TABLE IF NOT EXISTS transactions ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, branch_id BIGINT NOT NULL, cashier_id BIGINT NOT NULL, bet_id BIGINT NOT NULL, + type BIGINT NOT NULL, payment_option BIGINT NOT NULL, full_name VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, @@ -124,28 +161,228 @@ CREATE TABLE IF NOT EXISTS transactions ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); - - +CREATE TABLE IF NOT EXISTS branches ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + location VARCHAR(255) NOT NULL, + 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 +); +CREATE VIEW branch_details AS +SELECT branches.*, + CONCAT(users.first_name, ' ', users.last_name) AS manager_name, + users.phone_number AS manager_phone_number +FROM branches + LEFT JOIN users ON branches.branch_manager_id = users.id; +CREATE TABLE IF NOT EXISTS supported_operations ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description VARCHAR(255) NOT NULL +); +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 events ( + id TEXT PRIMARY KEY, + sport_id TEXT, + match_name TEXT, + home_team TEXT, + away_team TEXT, + home_team_id TEXT, + away_team_id TEXT, + home_kit_image TEXT, + away_kit_image TEXT, + league_id TEXT, + 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() +); +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 +); +ALTER TABLE refresh_tokens +ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); +ALTER TABLE bets +ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), + ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); +ALTER TABLE wallets +ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); +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 transactions +ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), + ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), + ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(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); +ALTER TABLE branch_operations +ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id), + ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id); +ALTER TABLE branch_cashiers +ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id), + ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id); ----------------------------------------------seed data------------------------------------------------------------- -------------------------------------- DO NOT USE IN PRODUCTION------------------------------------------------- - CREATE EXTENSION IF NOT EXISTS pgcrypto; - INSERT INTO users ( - first_name, last_name, email, phone_number, password, role, - email_verified, phone_verified, created_at, updated_at, - suspended_at, suspended -) VALUES ( - 'John', - 'Doe', - 'john.doe@example.com', - NULL, - crypt('password123', gen_salt('bf'))::bytea, - 'customer', - TRUE, - FALSE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - NULL, - FALSE -); + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended_at, + suspended + ) +VALUES ( + 'John', + 'Doe', + 'john.doe@example.com', + NULL, + crypt('password123', gen_salt('bf'))::bytea, + 'customer', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + NULL, + FALSE + ); +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended_at, + suspended + ) +VALUES ( + 'Samuel', + 'Tariku', + 'cybersamt@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'super_admin', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + NULL, + FALSE + ); +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended_at, + suspended + ) +VALUES ( + 'Kirubel', + 'Kibru', + 'kirubeljkl679 @gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'super_admin', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + NULL, + FALSE + ); +INSERT INTO supported_operations (name, description) +VALUES ('SportBook', 'Sportbook operations'), + ('Virtual', 'Virtual operations'), + ('GameZone', 'GameZone operations'); +INSERT INTO wallets ( + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + is_active, + created_at, + updated_at + ) +VALUES ( + 10000, + TRUE, + TRUE, + TRUE, + 1, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ); +--------------------------------------------------Bet365 Data Fetching + Event Managment------------------------------------------------ \ No newline at end of file diff --git a/db/query/bet.sql b/db/query/bet.sql index bf0d466..9ebbb30 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -1,16 +1,78 @@ -- name: CreateBet :one -INSERT INTO bets (amount, total_odds, status, full_name, phone_number, branch_id, user_id, is_shop_bet) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO bets ( + amount, + total_odds, + status, + full_name, + phone_number, + branch_id, + user_id, + is_shop_bet, + cashout_id + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; - +-- name: CreateBetOutcome :copyfrom +INSERT INTO bet_outcomes ( + bet_id, + event_id, + odd_id, + home_team_name, + away_team_name, + market_id, + market_name, + odd, + odd_name, + odd_header, + odd_handicap, + expires + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ); -- name: GetAllBets :many -SELECT * FROM bets; - +SELECT * +FROM bet_with_outcomes; -- name: GetBetByID :one -SELECT * FROM bets WHERE id = $1; - +SELECT * +FROM bet_with_outcomes +WHERE id = $1; +-- name: GetBetByCashoutID :one +SELECT * +FROM bet_with_outcomes +WHERE cashout_id = $1; +-- name: GetBetByBranchID :many +SELECT * +FROM bet_with_outcomes +WHERE branch_id = $1; -- name: UpdateCashOut :exec -UPDATE bets SET cashed_out = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; - +UPDATE bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; +-- name: UpdateBetOutcomeStatus :exec +UPDATE bet_outcomes +SET status = $1 +WHERE id = $2; +-- name: UpdateStatus :exec +UPDATE bets +SET status = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; -- name: DeleteBet :exec -DELETE FROM bets WHERE id = $1; +DELETE FROM bets +WHERE id = $1; +-- name: DeleteBetOutcome :exec +DELETE FROM bet_outcomes +WHERE bet_id = $1; \ No newline at end of file diff --git a/db/query/branch.sql b/db/query/branch.sql new file mode 100644 index 0000000..041d0ef --- /dev/null +++ b/db/query/branch.sql @@ -0,0 +1,86 @@ +-- name: CreateBranch :one +INSERT INTO branches ( + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; +-- name: CreateSupportedOperation :one +INSERT INTO supported_operations (name, description) +VALUES ($1, $2) +RETURNING *; +-- name: CreateBranchOperation :one +INSERT INTO branch_operations (operation_id, branch_id) +VALUES ($1, $2) +RETURNING *; +-- name: CreateBranchCashier :one +INSERT INTO branch_cashiers (user_id, branch_id) +VALUES ($1, $2) +RETURNING *; +-- name: GetAllBranches :many +SELECT * +FROM branch_details; + +-- name: GetBranchByID :one +SELECT * +FROM branch_details +WHERE id = $1; +-- name: GetBranchByCompanyID :many +SELECT * +FROM branch_details +WHERE company_id = $1; +-- name: GetBranchByManagerID :many +SELECT * +FROM branch_details +WHERE branch_manager_id = $1; +-- name: SearchBranchByName :many +SELECT * +FROM branch_details +WHERE name ILIKE '%' || $1 || '%'; +-- name: GetAllSupportedOperations :many +SELECT * +FROM supported_operations; +-- name: GetBranchOperations :many +SELECT branch_operations.*, + supported_operations.name, + supported_operations.description +FROM branch_operations + JOIN supported_operations ON branch_operations.operation_id = supported_operations.id +WHERE branch_operations.branch_id = $1; +-- name: GetBranchByCashier :one +SELECT branches.* +FROM branch_cashiers + JOIN branches ON branch_cashiers.branch_id = branches.id +WHERE branch_cashiers.user_id = $1; +-- name: GetCashiersByBranch :many +SELECT users.* +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1; +-- name: GetAllCashiers :many +SELECT users.* +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id; +-- name: UpdateBranch :one +UPDATE branches +SET name = $1, + location = $2, + branch_manager_id = $3, + company_id = $4, + is_self_owned = $5 +WHERE id = $6 +RETURNING *; +-- name: DeleteBranch :exec +DELETE FROM branches +WHERE id = $1; +-- name: DeleteBranchOperation :exec +DELETE FROM branch_operations +WHERE operation_id = $1 + AND branch_id = $2; +-- name: DeleteBranchCashier :exec +DELETE FROM branch_cashiers +WHERE user_id = $1; \ No newline at end of file diff --git a/db/query/company.sql b/db/query/company.sql new file mode 100644 index 0000000..d82cb7a --- /dev/null +++ b/db/query/company.sql @@ -0,0 +1,24 @@ +-- name: CreateCompany :one +INSERT INTO companies ( + name, + admin_id, + wallet_id + ) +VALUES ($1, $2, $3) +RETURNING *; +-- name: GetAllCompanies :many +SELECT * +FROM companies; +-- name: GetCompanyByID :one +SELECT * +FROM companies +WHERE id = $1; +-- name: UpdateCompany :one +UPDATE companies +SET name = $1, + admin_id = $2 +WHERE id = $3 +RETURNING *; +-- name: DeleteCompany :exec +DELETE FROM companies +WHERE id = $1; \ No newline at end of file diff --git a/db/query/events.sql b/db/query/events.sql new file mode 100644 index 0000000..ab459ae --- /dev/null +++ b/db/query/events.sql @@ -0,0 +1,207 @@ +-- name: InsertEvent :exec +INSERT INTO events ( + id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + score, + match_minute, + timer_status, + added_time, + match_period, + is_live, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + $14, + $15, + $16, + $17, + $18, + $19, + $20 + ) ON CONFLICT (id) DO +UPDATE +SET sport_id = EXCLUDED.sport_id, + match_name = EXCLUDED.match_name, + home_team = EXCLUDED.home_team, + away_team = EXCLUDED.away_team, + home_team_id = EXCLUDED.home_team_id, + away_team_id = EXCLUDED.away_team_id, + home_kit_image = EXCLUDED.home_kit_image, + away_kit_image = EXCLUDED.away_kit_image, + league_id = EXCLUDED.league_id, + league_name = EXCLUDED.league_name, + league_cc = EXCLUDED.league_cc, + start_time = EXCLUDED.start_time, + score = EXCLUDED.score, + match_minute = EXCLUDED.match_minute, + timer_status = EXCLUDED.timer_status, + added_time = EXCLUDED.added_time, + match_period = EXCLUDED.match_period, + is_live = EXCLUDED.is_live, + status = EXCLUDED.status, + fetched_at = now(); +-- name: InsertUpcomingEvent :exec +INSERT INTO events ( + id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + false, + 'upcoming' + ) ON CONFLICT (id) DO +UPDATE +SET sport_id = EXCLUDED.sport_id, + match_name = EXCLUDED.match_name, + home_team = EXCLUDED.home_team, + away_team = EXCLUDED.away_team, + home_team_id = EXCLUDED.home_team_id, + away_team_id = EXCLUDED.away_team_id, + home_kit_image = EXCLUDED.home_kit_image, + away_kit_image = EXCLUDED.away_kit_image, + league_id = EXCLUDED.league_id, + league_name = EXCLUDED.league_name, + league_cc = EXCLUDED.league_cc, + start_time = EXCLUDED.start_time, + is_live = false, + status = 'upcoming', + fetched_at = now(); +-- name: ListLiveEvents :many +SELECT id +FROM events +WHERE is_live = true; +-- name: GetAllUpcomingEvents :many +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE is_live = false + AND status = 'upcoming' +ORDER BY start_time ASC; +-- name: GetTotalEvents :one +SELECT COUNT(*) +FROM events +WHERE is_live = false + AND status = 'upcoming' + AND ( + league_id = $1 + OR $1 IS NULL + ) + AND ( + sport_id = $2 + OR $2 IS NULL + ); +-- name: GetPaginatedUpcomingEvents :many +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE is_live = false + AND status = 'upcoming' + AND ( + league_id = $3 + OR $3 IS NULL + ) + AND ( + sport_id = $4 + OR $4 IS NULL + ) +ORDER BY start_time ASC +LIMIT $1 OFFSET $2; +-- name: GetUpcomingByID :one +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE id = $1 + AND is_live = false + AND status = 'upcoming' +LIMIT 1; \ No newline at end of file diff --git a/db/query/odds.sql b/db/query/odds.sql new file mode 100644 index 0000000..908a445 --- /dev/null +++ b/db/query/odds.sql @@ -0,0 +1,121 @@ +-- name: InsertNonLiveOdd :exec +INSERT INTO odds ( + event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + is_active, + source, + fetched_at + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + $14, + $15 + ) ON CONFLICT (event_id, market_id) DO +UPDATE +SET odds_value = EXCLUDED.odds_value, + raw_odds = EXCLUDED.raw_odds, + market_type = EXCLUDED.market_type, + market_name = EXCLUDED.market_name, + market_category = EXCLUDED.market_category, + name = EXCLUDED.name, + handicap = EXCLUDED.handicap, + fetched_at = EXCLUDED.fetched_at, + is_active = EXCLUDED.is_active, + source = EXCLUDED.source, + fi = EXCLUDED.fi; +-- name: GetPrematchOdds :many +SELECT event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + fetched_at, + source, + is_active +FROM odds +WHERE is_active = true + AND source = 'b365api'; +-- name: GetALLPrematchOdds :many +SELECT event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + fetched_at, + source, + is_active +FROM odds +WHERE is_active = true + AND source = 'b365api'; +-- name: GetRawOddsByMarketID :one +SELECT id, + market_name, + handicap, + raw_odds, + fetched_at +FROM odds +WHERE market_id = $1 + AND fi = $2 + AND is_active = true + AND source = 'b365api'; + +-- name: GetPrematchOddsByUpcomingID :many +SELECT o.event_id, + o.fi, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.name, + o.handicap, + o.odds_value, + o.section, + o.category, + o.raw_odds, + o.fetched_at, + o.source, + o.is_active +FROM odds o + JOIN events e ON o.fi = e.id +WHERE e.id = $1 + AND e.is_live = false + AND e.status = 'upcoming' + AND o.is_active = true + AND o.source = 'b365api' +LIMIT $2 OFFSET $3; \ No newline at end of file diff --git a/db/query/ticket.sql b/db/query/ticket.sql index 04be763..8e2daaf 100644 --- a/db/query/ticket.sql +++ b/db/query/ticket.sql @@ -2,15 +2,56 @@ INSERT INTO tickets (amount, total_odds) VALUES ($1, $2) RETURNING *; - +-- name: CreateTicketOutcome :copyfrom +INSERT INTO ticket_outcomes ( + ticket_id, + event_id, + odd_id, + home_team_name, + away_team_name, + market_id, + market_name, + odd, + odd_name, + odd_header, + odd_handicap, + expires + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ); -- name: GetAllTickets :many -SELECT * FROM tickets; - +SELECT * +FROM ticket_with_outcomes; -- name: GetTicketByID :one -SELECT * FROM tickets WHERE id = $1; - +SELECT * +FROM ticket_with_outcomes +WHERE id = $1; +-- name: GetTicketOutcome :many +SELECT * +FROM ticket_outcomes +WHERE ticket_id = $1; +-- name: UpdateTicketOutcomeStatus :exec +UPDATE ticket_outcomes +SET status = $1 +WHERE id = $2; -- name: DeleteTicket :exec -DELETE FROM tickets WHERE id = $1; - +DELETE FROM tickets +WHERE id = $1; -- name: DeleteOldTickets :exec -Delete from tickets where created_at < now() - interval '1 day'; +Delete from tickets +where created_at < now() - interval '1 day'; +-- name: DeleteTicketOutcome :exec +Delete from ticket_outcomes +where ticket_id = $1; \ No newline at end of file diff --git a/db/query/transactions.sql b/db/query/transactions.sql index 2665eaa..a5d21b0 100644 --- a/db/query/transactions.sql +++ b/db/query/transactions.sql @@ -1,12 +1,15 @@ -- name: CreateTransaction :one -INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; +INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *; -- name: GetAllTransactions :many SELECT * FROM transactions; --- name: GetTransactionByID :one +-- name: GetTransactionByID :one SELECT * FROM transactions WHERE id = $1; +-- name: GetTransactionByBranch :many +SELECT * FROM transactions WHERE branch_id = $1; + -- name: UpdateTransactionVerified :exec UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; diff --git a/db/query/transfer.sql b/db/query/transfer.sql index 895ccb8..62007d6 100644 --- a/db/query/transfer.sql +++ b/db/query/transfer.sql @@ -1,11 +1,11 @@ -- name: CreateTransfer :one -INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING *; +INSERT INTO wallet_transfer (amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *; -- name: GetAllTransfers :many SELECT * FROM wallet_transfer; -- name: GetTransfersByWallet :many -SELECT * FROM wallet_transfer WHERE wallet_id = $1; +SELECT * FROM wallet_transfer WHERE receiver_wallet_id = $1 OR sender_wallet_id = $1; -- name: GetTransferByID :one SELECT * FROM wallet_transfer WHERE id = $1; diff --git a/db/query/user.sql b/db/query/user.sql index 04cbf84..bee4713 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -1,42 +1,114 @@ -- name: CreateUser :one - -INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at) +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + role, + password, + email_verified, + phone_verified, + created_at, + updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) -RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at; - +RETURNING id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at; -- name: GetUserByID :one SELECT * FROM users WHERE id = $1; - -- name: GetAllUsers :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users; - +-- name: SearchUserByNameOrPhone :many +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at +FROM users +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' + OR phone_number LIKE '%' || $1 || '%'; -- name: UpdateUser :exec UPDATE users -SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = $6 +SET first_name = $1, + last_name = $2, + email = $3, + phone_number = $4, + role = $5, + updated_at = $6 WHERE id = $7; - -- name: DeleteUser :exec DELETE FROM users WHERE id = $1; - -- name: CheckPhoneEmailExist :one -SELECT - EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists, - EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists; +SELECT EXISTS ( + SELECT 1 + FROM users + WHERE users.phone_number = $1 + AND users.phone_number IS NOT NULL + ) AS phone_exists, + EXISTS ( + SELECT 1 + FROM users + WHERE users.email = $2 + AND users.email IS NOT NULL + ) AS email_exists; -- name: GetUserByEmail :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE email = $1; - -- name: GetUserByPhone :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE phone_number = $1; - -- name: UpdatePassword :exec UPDATE users -SET password = $1, updated_at = $4 -WHERE (email = $2 OR phone_number = $3); \ No newline at end of file +SET password = $1, + updated_at = $4 +WHERE ( + email = $2 + OR phone_number = $3 + ); \ No newline at end of file diff --git a/db/query/wallet.sql b/db/query/wallet.sql index 46f3200..0f4d27d 100644 --- a/db/query/wallet.sql +++ b/db/query/wallet.sql @@ -1,21 +1,34 @@ -- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING *; - +INSERT INTO wallets ( + is_withdraw, + is_bettable, + is_transferable, + user_id + ) +VALUES ($1, $2, $3, $4) +RETURNING *; -- name: CreateCustomerWallet :one -INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING *; - +INSERT INTO customer_wallets ( + customer_id, + company_id, + regular_wallet_id, + static_wallet_id + ) +VALUES ($1, $2, $3, $4) +RETURNING *; -- name: GetAllWallets :many -SELECT * FROM wallets; - +SELECT * +FROM wallets; -- name: GetWalletByID :one -SELECT * FROM wallets WHERE id = $1; - +SELECT * +FROM wallets +WHERE id = $1; -- name: GetWalletByUserID :many -SELECT * FROM wallets WHERE user_id = $1; - +SELECT * +FROM wallets +WHERE user_id = $1; -- name: GetCustomerWallet :one -SELECT - cw.id, +SELECT cw.id, cw.customer_id, cw.company_id, rw.id AS regular_id, @@ -26,15 +39,30 @@ SELECT sw.updated_at as static_updated_at, cw.created_at FROM customer_wallets cw -JOIN wallets rw ON cw.regular_wallet_id = rw.id -JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1 AND cw.company_id = $2; - + JOIN wallets rw ON cw.regular_wallet_id = rw.id + JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 + AND cw.company_id = $2; +-- name: GetAllBranchWallets :many +SELECT wallets.id, + wallets.balance, + wallets.is_active, + wallets.updated_at, + wallets.created_at, + branches.name, + branches.location, + branches.branch_manager_id, + branches.company_id, + branches.is_self_owned +FROM branches + JOIN wallets ON branches.wallet_id = wallets.id; -- name: UpdateBalance :exec -UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; - +UPDATE wallets +SET balance = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2; -- name: UpdateWalletActive :exec -UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; - - - +UPDATE wallets +SET is_active = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2; \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 36464a6..cb1802c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -262,6 +262,50 @@ const docTemplate = `{ } } }, + "/bet/cashout/{id}": { + "get": { + "description": "Gets a single bet by cashout id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Gets bet by cashout id", + "parameters": [ + { + "type": "string", + "description": "cashout ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BetRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/bet/{id}": { "get": { "description": "Gets a single bet by id", @@ -399,6 +443,1495 @@ const docTemplate = `{ } } }, + "/branch": { + "get": { + "description": "Gets all branches", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all branches", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a branch", + "parameters": [ + { + "description": "Creates branch", + "name": "createBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}": { + "get": { + "description": "Gets a single branch by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch by id", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Updates a branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Branch", + "name": "updateBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/bets": { + "get": { + "description": "Gets bets by its branch id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets bets by its branch id", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BetRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation": { + "get": { + "description": "Gets branch operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch operations", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation/{opID}": { + "delete": { + "description": "Delete the branch operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch operation", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Branch Operation ID", + "name": "opID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branchWallet": { + "get": { + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all branch wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/cashiers": { + "get": { + "description": "Get all cashiers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Get all cashiers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Create cashier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Create cashier", + "parameters": [ + { + "description": "Create cashier", + "name": "cashier", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCashierReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/cashiers/{id}": { + "put": { + "description": "Update cashier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Update cashier", + "parameters": [ + { + "description": "Update cashier", + "name": "cashier", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateUserReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company": { + "get": { + "description": "Gets all companies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets all companies", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.CompanyRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Create a company", + "parameters": [ + { + "description": "Creates company", + "name": "createCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}": { + "get": { + "description": "Gets a single company by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets company by id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Updates a company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Company", + "name": "updateCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Delete the company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}/branch": { + "get": { + "description": "Gets branches by company id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by company id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/manager/{id}/branch": { + "get": { + "description": "Gets a branches by manager id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by manager id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/managers": { + "get": { + "description": "Get all Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "manager" + ], + "summary": "Get all Managers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Create Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "manager" + ], + "summary": "Create Managers", + "parameters": [ + { + "description": "Create manager", + "name": "manger", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateManagerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/managers/{id}": { + "put": { + "description": "Update Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Managers" + ], + "summary": "Update Managers", + "parameters": [ + { + "description": "Update Managers", + "name": "Managers", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateUserReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/operation": { + "post": { + "description": "Creates a operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a operation", + "parameters": [ + { + "description": "Creates operation", + "name": "createBranchOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/events": { + "get": { + "description": "Retrieve all upcoming events from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all upcoming events", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "League ID Filter", + "name": "league_id", + "in": "query" + }, + { + "type": "string", + "description": "Sport ID Filter", + "name": "sport_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.UpcomingEvent" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/events/{id}": { + "get": { + "description": "Retrieve an upcoming event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve an upcoming by ID", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpcomingEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds": { + "get": { + "description": "Retrieve all prematch odds from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all prematch odds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/upcoming/{upcoming_id}": { + "get": { + "description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve prematch odds by upcoming ID (FI)", + "parameters": [ + { + "type": "string", + "description": "Upcoming Event ID (FI)", + "name": "upcoming_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Number of results to return (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of results to skip (default: 0)", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/upcoming/{upcoming_id}/market/{market_id}": { + "get": { + "description": "Retrieve raw odds records using a Market ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve raw odds by Market ID", + "parameters": [ + { + "type": "string", + "description": "Upcoming ID", + "name": "upcoming_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Market ID", + "name": "market_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.RawOddsByMarketID" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/{event_id}": { + "get": { + "description": "Retrieve prematch odds for a specific event by event ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve prematch odds for an event", + "parameters": [ + { + "type": "string", + "description": "Event ID", + "name": "event_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/search/branch": { + "get": { + "description": "Search branches by name or location", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Search branches", + "parameters": [ + { + "type": "string", + "description": "Search query", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/supportedOperation": { + "get": { + "description": "Gets all supported operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all supported operations", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a supported operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a supported operation", + "parameters": [ + { + "description": "Creates supported operation", + "name": "createSupportedOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SupportedOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/ticket": { "get": { "description": "Retrieve all tickets", @@ -702,6 +2235,144 @@ const docTemplate = `{ } } }, + "/transfer/refill/:id": { + "post": { + "description": "Super Admin route to refill a wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Refill wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "refillWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/:id": { + "post": { + "description": "Create a transfer to wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Create a transfer to wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/{id}": { + "get": { + "description": "Get transfer by wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Get transfer by wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -880,6 +2551,52 @@ const docTemplate = `{ } } }, + "/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/sendRegisterCode": { "post": { "description": "Send register code", @@ -1156,7 +2873,123 @@ const docTemplate = `{ } }, "definitions": { - "domain.BetStatus": { + "domain.BetOutcome": { + "type": "object", + "properties": { + "away_team_name": { + "type": "string", + "example": "Liverpool" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "event_id": { + "type": "integer", + "example": 1 + }, + "expires": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "home_team_name": { + "type": "string", + "example": "Manchester" + }, + "id": { + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "market_name": { + "type": "string", + "example": "Fulltime Result" + }, + "odd": { + "type": "number", + "example": 1.5 + }, + "odd_handicap": { + "type": "string", + "example": "1" + }, + "odd_header": { + "type": "string", + "example": "1" + }, + "odd_id": { + "type": "integer", + "example": 1 + }, + "odd_name": { + "type": "string", + "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + } + } + }, + "domain.Odd": { + "type": "object", + "properties": { + "category": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "fetched_at": { + "type": "string" + }, + "fi": { + "type": "string" + }, + "handicap": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "market_category": { + "type": "string" + }, + "market_id": { + "type": "string" + }, + "market_name": { + "type": "string" + }, + "market_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "odds_value": { + "type": "number" + }, + "raw_odds": { + "type": "array", + "items": {} + }, + "section": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "domain.OutcomeStatus": { "type": "integer", "enum": [ 0, @@ -1165,15 +2998,12 @@ const docTemplate = `{ 3 ], "x-enum-varnames": [ - "BET_STATUS_PENDING", - "BET_STATUS_WIN", - "BET_STATUS_LOSS", - "BET_STATUS_ERROR" + "OUTCOME_STATUS_PENDING", + "OUTCOME_STATUS_WIN", + "OUTCOME_STATUS_LOSS", + "OUTCOME_STATUS_ERROR" ] }, - "domain.Outcome": { - "type": "object" - }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -1189,6 +3019,27 @@ const docTemplate = `{ "BANK" ] }, + "domain.RawOddsByMarketID": { + "type": "object", + "properties": { + "fetched_at": { + "type": "string" + }, + "handicap": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "market_name": { + "type": "string" + }, + "raw_odds": { + "type": "array", + "items": {} + } + } + }, "domain.Role": { "type": "string", "enum": [ @@ -1206,6 +3057,128 @@ const docTemplate = `{ "RoleCashier" ] }, + "domain.TicketOutcome": { + "type": "object", + "properties": { + "away_team_name": { + "type": "string", + "example": "Liverpool" + }, + "event_id": { + "type": "integer", + "example": 1 + }, + "expires": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "home_team_name": { + "type": "string", + "example": "Manchester" + }, + "id": { + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "market_name": { + "type": "string", + "example": "Fulltime Result" + }, + "odd": { + "type": "number", + "example": 1.5 + }, + "odd_handicap": { + "type": "string", + "example": "1" + }, + "odd_header": { + "type": "string", + "example": "1" + }, + "odd_id": { + "type": "integer", + "example": 1 + }, + "odd_name": { + "type": "string", + "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + }, + "ticket_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.UpcomingEvent": { + "type": "object", + "properties": { + "awayKitImage": { + "description": "Kit or image for away team (optional)", + "type": "string" + }, + "awayTeam": { + "description": "Away team name (can be empty/null)", + "type": "string" + }, + "awayTeamID": { + "description": "Away team ID (can be empty/null)", + "type": "string" + }, + "homeKitImage": { + "description": "Kit or image for home team (optional)", + "type": "string" + }, + "homeTeam": { + "description": "Home team name (if available)", + "type": "string" + }, + "homeTeamID": { + "description": "Home team ID", + "type": "string" + }, + "id": { + "description": "Event ID", + "type": "string" + }, + "leagueCC": { + "description": "League country code", + "type": "string" + }, + "leagueID": { + "description": "League ID", + "type": "string" + }, + "leagueName": { + "description": "League name", + "type": "string" + }, + "matchName": { + "description": "Match or event name", + "type": "string" + }, + "sportID": { + "description": "Sport ID", + "type": "string" + }, + "startTime": { + "description": "Converted from \"time\" field in UNIX format", + "type": "string" + } + } + }, "handlers.BetRes": { "type": "object", "properties": { @@ -1217,6 +3190,14 @@ const docTemplate = `{ "type": "integer", "example": 2 }, + "cashed_id": { + "type": "string", + "example": "21234" + }, + "cashed_out": { + "type": "boolean", + "example": false + }, "full_name": { "type": "string", "example": "John" @@ -1232,7 +3213,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.BetOutcome" } }, "phone_number": { @@ -1242,7 +3223,7 @@ const docTemplate = `{ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -1257,6 +3238,93 @@ const docTemplate = `{ } } }, + "handlers.BranchDetailRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -1281,6 +3349,45 @@ const docTemplate = `{ } } }, + "handlers.CompanyRes": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBetOutcomeReq": { + "type": "object", + "properties": { + "event_id": { + "description": "BetID int64 ` + "`" + `json:\"bet_id\" example:\"1\"` + "`" + `", + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateBetReq": { "type": "object", "properties": { @@ -1299,7 +3406,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.CreateBetOutcomeReq" } }, "phone_number": { @@ -1309,7 +3416,7 @@ const docTemplate = `{ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -1320,6 +3427,148 @@ const docTemplate = `{ } } }, + "handlers.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBranchReq": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "handlers.CreateCashierReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, + "handlers.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, + "handlers.CreateManagerReq": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, + "handlers.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.CreateTicketOutcomeReq": { + "type": "object", + "properties": { + "event_id": { + "description": "TicketID int64 ` + "`" + `json:\"ticket_id\" example:\"1\"` + "`" + `", + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateTicketReq": { "type": "object", "properties": { @@ -1330,7 +3579,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.CreateTicketOutcomeReq" } }, "total_odds": { @@ -1342,6 +3591,10 @@ const docTemplate = `{ "handlers.CreateTicketRes": { "type": "object", "properties": { + "created_number": { + "type": "integer", + "example": 3 + }, "fast_code": { "type": "integer", "example": 1234 @@ -1362,7 +3615,6 @@ const docTemplate = `{ "example": 100 }, "bank_code": { - "description": "Payment Details for bank", "type": "string" }, "beneficiary_name": { @@ -1372,13 +3624,9 @@ const docTemplate = `{ "type": "integer", "example": 1 }, - "branch_id": { - "type": "integer", - "example": 1 - }, - "cashier_id": { - "type": "integer", - "example": 1 + "cashout_id": { + "type": "string", + "example": "191212" }, "full_name": { "type": "string", @@ -1398,6 +3646,23 @@ const docTemplate = `{ }, "reference_number": { "type": "string" + }, + "type": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateTransferReq": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "payment_method": { + "type": "string", + "example": "cash" } } }, @@ -1520,6 +3785,31 @@ const docTemplate = `{ } } }, + "handlers.SearchUserByNameOrPhoneReq": { + "type": "object", + "properties": { + "searchString": { + "type": "string" + } + } + }, + "handlers.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -1534,7 +3824,7 @@ const docTemplate = `{ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.TicketOutcome" } }, "total_odds": { @@ -1597,6 +3887,55 @@ const docTemplate = `{ "reference_number": { "type": "string" }, + "type": { + "type": "integer", + "example": 1 + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, + "handlers.TransferWalletRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "cashier_id": { + "type": "integer", + "example": 789 + }, + "created_at": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_method": { + "type": "string", + "example": "bank" + }, + "receiver_wallet_id": { + "type": "integer", + "example": 1 + }, + "sender_wallet_id": { + "type": "integer", + "example": 1 + }, + "type": { + "type": "string", + "example": "transfer" + }, + "updated_at": { + "type": "string", + "example": "2025-04-08T12:30:00Z" + }, "verified": { "type": "boolean", "example": true @@ -1690,6 +4029,10 @@ const docTemplate = `{ "type": "boolean", "example": true }, + "is_transferable": { + "type": "boolean", + "example": true + }, "is_withdraw": { "type": "boolean", "example": true @@ -1728,6 +4071,9 @@ const docTemplate = `{ }, "refresh_token": { "type": "string" + }, + "role": { + "type": "string" } } }, @@ -1750,6 +4096,23 @@ const docTemplate = `{ } } }, + "handlers.updateUserReq": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "suspended": { + "type": "boolean", + "example": false + } + } + }, "response.APIResponse": { "type": "object", "properties": { @@ -1758,11 +4121,17 @@ const docTemplate = `{ "type": "string" }, "metadata": {}, + "page": { + "type": "integer" + }, "status": { "$ref": "#/definitions/response.Status" }, "timestamp": { "type": "string" + }, + "total": { + "type": "integer" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index cc51adb..ad7007a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -254,6 +254,50 @@ } } }, + "/bet/cashout/{id}": { + "get": { + "description": "Gets a single bet by cashout id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Gets bet by cashout id", + "parameters": [ + { + "type": "string", + "description": "cashout ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BetRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/bet/{id}": { "get": { "description": "Gets a single bet by id", @@ -391,6 +435,1495 @@ } } }, + "/branch": { + "get": { + "description": "Gets all branches", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all branches", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a branch", + "parameters": [ + { + "description": "Creates branch", + "name": "createBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}": { + "get": { + "description": "Gets a single branch by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch by id", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Updates a branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Branch", + "name": "updateBranch", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the branch", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/bets": { + "get": { + "description": "Gets bets by its branch id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets bets by its branch id", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BetRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation": { + "get": { + "description": "Gets branch operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branch operations", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branch/{id}/operation/{opID}": { + "delete": { + "description": "Delete the branch operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Delete the branch operation", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Branch Operation ID", + "name": "opID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/branchWallet": { + "get": { + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all branch wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/cashiers": { + "get": { + "description": "Get all cashiers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Get all cashiers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Create cashier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Create cashier", + "parameters": [ + { + "description": "Create cashier", + "name": "cashier", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCashierReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/cashiers/{id}": { + "put": { + "description": "Update cashier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "cashier" + ], + "summary": "Update cashier", + "parameters": [ + { + "description": "Update cashier", + "name": "cashier", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateUserReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company": { + "get": { + "description": "Gets all companies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets all companies", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.CompanyRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Create a company", + "parameters": [ + { + "description": "Creates company", + "name": "createCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}": { + "get": { + "description": "Gets a single company by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Gets company by id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Updates a company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Updates a company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Company", + "name": "updateCompany", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateCompanyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CompanyRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete the company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "company" + ], + "summary": "Delete the company", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/company/{id}/branch": { + "get": { + "description": "Gets branches by company id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by company id", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/manager/{id}/branch": { + "get": { + "description": "Gets a branches by manager id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets branches by manager id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/managers": { + "get": { + "description": "Get all Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "manager" + ], + "summary": "Get all Managers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Create Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "manager" + ], + "summary": "Create Managers", + "parameters": [ + { + "description": "Create manager", + "name": "manger", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateManagerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/managers/{id}": { + "put": { + "description": "Update Managers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Managers" + ], + "summary": "Update Managers", + "parameters": [ + { + "description": "Update Managers", + "name": "Managers", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateUserReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/operation": { + "post": { + "description": "Creates a operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a operation", + "parameters": [ + { + "description": "Creates operation", + "name": "createBranchOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateBranchOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.BranchOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/events": { + "get": { + "description": "Retrieve all upcoming events from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all upcoming events", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "League ID Filter", + "name": "league_id", + "in": "query" + }, + { + "type": "string", + "description": "Sport ID Filter", + "name": "sport_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.UpcomingEvent" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/events/{id}": { + "get": { + "description": "Retrieve an upcoming event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve an upcoming by ID", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpcomingEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds": { + "get": { + "description": "Retrieve all prematch odds from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all prematch odds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/upcoming/{upcoming_id}": { + "get": { + "description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve prematch odds by upcoming ID (FI)", + "parameters": [ + { + "type": "string", + "description": "Upcoming Event ID (FI)", + "name": "upcoming_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Number of results to return (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of results to skip (default: 0)", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/upcoming/{upcoming_id}/market/{market_id}": { + "get": { + "description": "Retrieve raw odds records using a Market ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve raw odds by Market ID", + "parameters": [ + { + "type": "string", + "description": "Upcoming ID", + "name": "upcoming_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Market ID", + "name": "market_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.RawOddsByMarketID" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/{event_id}": { + "get": { + "description": "Retrieve prematch odds for a specific event by event ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve prematch odds for an event", + "parameters": [ + { + "type": "string", + "description": "Event ID", + "name": "event_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/search/branch": { + "get": { + "description": "Search branches by name or location", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Search branches", + "parameters": [ + { + "type": "string", + "description": "Search query", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/supportedOperation": { + "get": { + "description": "Gets all supported operations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Gets all supported operations", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.BranchDetailRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a supported operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Create a supported operation", + "parameters": [ + { + "description": "Creates supported operation", + "name": "createSupportedOperation", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.SupportedOperationRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/ticket": { "get": { "description": "Retrieve all tickets", @@ -694,6 +2227,144 @@ } } }, + "/transfer/refill/:id": { + "post": { + "description": "Super Admin route to refill a wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Refill wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "refillWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/:id": { + "post": { + "description": "Create a transfer to wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Create a transfer to wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transfer/wallet/{id}": { + "get": { + "description": "Get transfer by wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfer" + ], + "summary": "Get transfer by wallet", + "parameters": [ + { + "description": "Create Transfer", + "name": "transferToWallet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransferReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransferWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -872,6 +2543,52 @@ } } }, + "/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/sendRegisterCode": { "post": { "description": "Send register code", @@ -1148,7 +2865,123 @@ } }, "definitions": { - "domain.BetStatus": { + "domain.BetOutcome": { + "type": "object", + "properties": { + "away_team_name": { + "type": "string", + "example": "Liverpool" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "event_id": { + "type": "integer", + "example": 1 + }, + "expires": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "home_team_name": { + "type": "string", + "example": "Manchester" + }, + "id": { + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "market_name": { + "type": "string", + "example": "Fulltime Result" + }, + "odd": { + "type": "number", + "example": 1.5 + }, + "odd_handicap": { + "type": "string", + "example": "1" + }, + "odd_header": { + "type": "string", + "example": "1" + }, + "odd_id": { + "type": "integer", + "example": 1 + }, + "odd_name": { + "type": "string", + "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + } + } + }, + "domain.Odd": { + "type": "object", + "properties": { + "category": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "fetched_at": { + "type": "string" + }, + "fi": { + "type": "string" + }, + "handicap": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "market_category": { + "type": "string" + }, + "market_id": { + "type": "string" + }, + "market_name": { + "type": "string" + }, + "market_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "odds_value": { + "type": "number" + }, + "raw_odds": { + "type": "array", + "items": {} + }, + "section": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "domain.OutcomeStatus": { "type": "integer", "enum": [ 0, @@ -1157,15 +2990,12 @@ 3 ], "x-enum-varnames": [ - "BET_STATUS_PENDING", - "BET_STATUS_WIN", - "BET_STATUS_LOSS", - "BET_STATUS_ERROR" + "OUTCOME_STATUS_PENDING", + "OUTCOME_STATUS_WIN", + "OUTCOME_STATUS_LOSS", + "OUTCOME_STATUS_ERROR" ] }, - "domain.Outcome": { - "type": "object" - }, "domain.PaymentOption": { "type": "integer", "enum": [ @@ -1181,6 +3011,27 @@ "BANK" ] }, + "domain.RawOddsByMarketID": { + "type": "object", + "properties": { + "fetched_at": { + "type": "string" + }, + "handicap": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "market_name": { + "type": "string" + }, + "raw_odds": { + "type": "array", + "items": {} + } + } + }, "domain.Role": { "type": "string", "enum": [ @@ -1198,6 +3049,128 @@ "RoleCashier" ] }, + "domain.TicketOutcome": { + "type": "object", + "properties": { + "away_team_name": { + "type": "string", + "example": "Liverpool" + }, + "event_id": { + "type": "integer", + "example": 1 + }, + "expires": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "home_team_name": { + "type": "string", + "example": "Manchester" + }, + "id": { + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "market_name": { + "type": "string", + "example": "Fulltime Result" + }, + "odd": { + "type": "number", + "example": 1.5 + }, + "odd_handicap": { + "type": "string", + "example": "1" + }, + "odd_header": { + "type": "string", + "example": "1" + }, + "odd_id": { + "type": "integer", + "example": 1 + }, + "odd_name": { + "type": "string", + "example": "1" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/domain.OutcomeStatus" + } + ], + "example": 1 + }, + "ticket_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.UpcomingEvent": { + "type": "object", + "properties": { + "awayKitImage": { + "description": "Kit or image for away team (optional)", + "type": "string" + }, + "awayTeam": { + "description": "Away team name (can be empty/null)", + "type": "string" + }, + "awayTeamID": { + "description": "Away team ID (can be empty/null)", + "type": "string" + }, + "homeKitImage": { + "description": "Kit or image for home team (optional)", + "type": "string" + }, + "homeTeam": { + "description": "Home team name (if available)", + "type": "string" + }, + "homeTeamID": { + "description": "Home team ID", + "type": "string" + }, + "id": { + "description": "Event ID", + "type": "string" + }, + "leagueCC": { + "description": "League country code", + "type": "string" + }, + "leagueID": { + "description": "League ID", + "type": "string" + }, + "leagueName": { + "description": "League name", + "type": "string" + }, + "matchName": { + "description": "Match or event name", + "type": "string" + }, + "sportID": { + "description": "Sport ID", + "type": "string" + }, + "startTime": { + "description": "Converted from \"time\" field in UNIX format", + "type": "string" + } + } + }, "handlers.BetRes": { "type": "object", "properties": { @@ -1209,6 +3182,14 @@ "type": "integer", "example": 2 }, + "cashed_id": { + "type": "string", + "example": "21234" + }, + "cashed_out": { + "type": "boolean", + "example": false + }, "full_name": { "type": "string", "example": "John" @@ -1224,7 +3205,7 @@ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.BetOutcome" } }, "phone_number": { @@ -1234,7 +3215,7 @@ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -1249,6 +3230,93 @@ } } }, + "handlers.BranchDetailRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -1273,6 +3341,45 @@ } } }, + "handlers.CompanyRes": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBetOutcomeReq": { + "type": "object", + "properties": { + "event_id": { + "description": "BetID int64 `json:\"bet_id\" example:\"1\"`", + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateBetReq": { "type": "object", "properties": { @@ -1291,7 +3398,7 @@ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.CreateBetOutcomeReq" } }, "phone_number": { @@ -1301,7 +3408,7 @@ "status": { "allOf": [ { - "$ref": "#/definitions/domain.BetStatus" + "$ref": "#/definitions/domain.OutcomeStatus" } ], "example": 1 @@ -1312,6 +3419,148 @@ } } }, + "handlers.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateBranchReq": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "handlers.CreateCashierReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, + "handlers.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, + "handlers.CreateManagerReq": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, + "handlers.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "handlers.CreateTicketOutcomeReq": { + "type": "object", + "properties": { + "event_id": { + "description": "TicketID int64 `json:\"ticket_id\" example:\"1\"`", + "type": "integer", + "example": 1 + }, + "market_id": { + "type": "integer", + "example": 1 + }, + "odd_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.CreateTicketReq": { "type": "object", "properties": { @@ -1322,7 +3571,7 @@ "outcomes": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/handlers.CreateTicketOutcomeReq" } }, "total_odds": { @@ -1334,6 +3583,10 @@ "handlers.CreateTicketRes": { "type": "object", "properties": { + "created_number": { + "type": "integer", + "example": 3 + }, "fast_code": { "type": "integer", "example": 1234 @@ -1354,7 +3607,6 @@ "example": 100 }, "bank_code": { - "description": "Payment Details for bank", "type": "string" }, "beneficiary_name": { @@ -1364,13 +3616,9 @@ "type": "integer", "example": 1 }, - "branch_id": { - "type": "integer", - "example": 1 - }, - "cashier_id": { - "type": "integer", - "example": 1 + "cashout_id": { + "type": "string", + "example": "191212" }, "full_name": { "type": "string", @@ -1390,6 +3638,23 @@ }, "reference_number": { "type": "string" + }, + "type": { + "type": "integer", + "example": 1 + } + } + }, + "handlers.CreateTransferReq": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "payment_method": { + "type": "string", + "example": "cash" } } }, @@ -1512,6 +3777,31 @@ } } }, + "handlers.SearchUserByNameOrPhoneReq": { + "type": "object", + "properties": { + "searchString": { + "type": "string" + } + } + }, + "handlers.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "handlers.TicketRes": { "type": "object", "properties": { @@ -1526,7 +3816,7 @@ "outcomes": { "type": "array", "items": { - "$ref": "#/definitions/domain.Outcome" + "$ref": "#/definitions/domain.TicketOutcome" } }, "total_odds": { @@ -1589,6 +3879,55 @@ "reference_number": { "type": "string" }, + "type": { + "type": "integer", + "example": 1 + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, + "handlers.TransferWalletRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "cashier_id": { + "type": "integer", + "example": 789 + }, + "created_at": { + "type": "string", + "example": "2025-04-08T12:00:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_method": { + "type": "string", + "example": "bank" + }, + "receiver_wallet_id": { + "type": "integer", + "example": 1 + }, + "sender_wallet_id": { + "type": "integer", + "example": 1 + }, + "type": { + "type": "string", + "example": "transfer" + }, + "updated_at": { + "type": "string", + "example": "2025-04-08T12:30:00Z" + }, "verified": { "type": "boolean", "example": true @@ -1682,6 +4021,10 @@ "type": "boolean", "example": true }, + "is_transferable": { + "type": "boolean", + "example": true + }, "is_withdraw": { "type": "boolean", "example": true @@ -1720,6 +4063,9 @@ }, "refresh_token": { "type": "string" + }, + "role": { + "type": "string" } } }, @@ -1742,6 +4088,23 @@ } } }, + "handlers.updateUserReq": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "suspended": { + "type": "boolean", + "example": false + } + } + }, "response.APIResponse": { "type": "object", "properties": { @@ -1750,11 +4113,17 @@ "type": "string" }, "metadata": {}, + "page": { + "type": "integer" + }, "status": { "$ref": "#/definitions/response.Status" }, "timestamp": { "type": "string" + }, + "total": { + "type": "integer" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9b8b3d4..cfa3120 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,85 @@ definitions: - domain.BetStatus: + domain.BetOutcome: + properties: + away_team_name: + example: Liverpool + type: string + bet_id: + example: 1 + type: integer + event_id: + example: 1 + type: integer + expires: + example: "2025-04-08T12:00:00Z" + type: string + home_team_name: + example: Manchester + type: string + id: + example: 1 + type: integer + market_id: + example: 1 + type: integer + market_name: + example: Fulltime Result + type: string + odd: + example: 1.5 + type: number + odd_handicap: + example: "1" + type: string + odd_header: + example: "1" + type: string + odd_id: + example: 1 + type: integer + odd_name: + example: "1" + type: string + status: + allOf: + - $ref: '#/definitions/domain.OutcomeStatus' + example: 1 + type: object + domain.Odd: + properties: + category: + type: string + event_id: + type: string + fetched_at: + type: string + fi: + type: string + handicap: + type: string + is_active: + type: boolean + market_category: + type: string + market_id: + type: string + market_name: + type: string + market_type: + type: string + name: + type: string + odds_value: + type: number + raw_odds: + items: {} + type: array + section: + type: string + source: + type: string + type: object + domain.OutcomeStatus: enum: - 0 - 1 @@ -7,12 +87,10 @@ definitions: - 3 type: integer x-enum-varnames: - - BET_STATUS_PENDING - - BET_STATUS_WIN - - BET_STATUS_LOSS - - BET_STATUS_ERROR - domain.Outcome: - type: object + - OUTCOME_STATUS_PENDING + - OUTCOME_STATUS_WIN + - OUTCOME_STATUS_LOSS + - OUTCOME_STATUS_ERROR domain.PaymentOption: enum: - 0 @@ -25,6 +103,20 @@ definitions: - TELEBIRR_TRANSACTION - ARIFPAY_TRANSACTION - BANK + domain.RawOddsByMarketID: + properties: + fetched_at: + type: string + handicap: + type: string + id: + type: integer + market_name: + type: string + raw_odds: + items: {} + type: array + type: object domain.Role: enum: - super_admin @@ -39,6 +131,94 @@ definitions: - RoleBranchManager - RoleCustomer - RoleCashier + domain.TicketOutcome: + properties: + away_team_name: + example: Liverpool + type: string + event_id: + example: 1 + type: integer + expires: + example: "2025-04-08T12:00:00Z" + type: string + home_team_name: + example: Manchester + type: string + id: + example: 1 + type: integer + market_id: + example: 1 + type: integer + market_name: + example: Fulltime Result + type: string + odd: + example: 1.5 + type: number + odd_handicap: + example: "1" + type: string + odd_header: + example: "1" + type: string + odd_id: + example: 1 + type: integer + odd_name: + example: "1" + type: string + status: + allOf: + - $ref: '#/definitions/domain.OutcomeStatus' + example: 1 + ticket_id: + example: 1 + type: integer + type: object + domain.UpcomingEvent: + properties: + awayKitImage: + description: Kit or image for away team (optional) + type: string + awayTeam: + description: Away team name (can be empty/null) + type: string + awayTeamID: + description: Away team ID (can be empty/null) + type: string + homeKitImage: + description: Kit or image for home team (optional) + type: string + homeTeam: + description: Home team name (if available) + type: string + homeTeamID: + description: Home team ID + type: string + id: + description: Event ID + type: string + leagueCC: + description: League country code + type: string + leagueID: + description: League ID + type: string + leagueName: + description: League name + type: string + matchName: + description: Match or event name + type: string + sportID: + description: Sport ID + type: string + startTime: + description: Converted from "time" field in UNIX format + type: string + type: object handlers.BetRes: properties: amount: @@ -47,6 +227,12 @@ definitions: branch_id: example: 2 type: integer + cashed_id: + example: "21234" + type: string + cashed_out: + example: false + type: boolean full_name: example: John type: string @@ -58,14 +244,14 @@ definitions: type: boolean outcomes: items: - $ref: '#/definitions/domain.Outcome' + $ref: '#/definitions/domain.BetOutcome' type: array phone_number: example: "1234567890" type: string status: allOf: - - $ref: '#/definitions/domain.BetStatus' + - $ref: '#/definitions/domain.OutcomeStatus' example: 1 total_odds: example: 4.22 @@ -74,6 +260,69 @@ definitions: example: 2 type: integer type: object + handlers.BranchDetailRes: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + manager_name: + example: John Smith + type: string + manager_phone_number: + example: "0911111111" + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object + handlers.BranchOperationRes: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string + type: object + handlers.BranchRes: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object handlers.CheckPhoneEmailExistReq: properties: email: @@ -90,6 +339,34 @@ definitions: phone_number_exist: type: boolean type: object + handlers.CompanyRes: + properties: + admin_id: + example: 1 + type: integer + id: + example: 1 + type: integer + name: + example: CompanyName + type: string + wallet_id: + example: 1 + type: integer + type: object + handlers.CreateBetOutcomeReq: + properties: + event_id: + description: BetID int64 `json:"bet_id" example:"1"` + example: 1 + type: integer + market_id: + example: 1 + type: integer + odd_id: + example: 1 + type: integer + type: object handlers.CreateBetReq: properties: amount: @@ -103,19 +380,120 @@ definitions: type: boolean outcomes: items: - type: integer + $ref: '#/definitions/handlers.CreateBetOutcomeReq' type: array phone_number: example: "1234567890" type: string status: allOf: - - $ref: '#/definitions/domain.BetStatus' + - $ref: '#/definitions/domain.OutcomeStatus' example: 1 total_odds: example: 4.22 type: number type: object + handlers.CreateBranchOperationReq: + properties: + branch_id: + example: 1 + type: integer + operation_id: + example: 1 + type: integer + type: object + handlers.CreateBranchReq: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + name: + example: 4-kilo Branch + type: string + operations: + items: + type: integer + type: array + type: object + handlers.CreateCashierReq: + properties: + branch_id: + example: 1 + type: integer + email: + example: john.doe@example.com + type: string + first_name: + example: John + type: string + last_name: + example: Doe + type: string + password: + example: password123 + type: string + phone_number: + example: "1234567890" + type: string + type: object + handlers.CreateCompanyReq: + properties: + admin_id: + example: 1 + type: integer + name: + example: CompanyName + type: string + type: object + handlers.CreateManagerReq: + properties: + email: + example: john.doe@example.com + type: string + first_name: + example: John + type: string + last_name: + example: Doe + type: string + password: + example: password123 + type: string + phone_number: + example: "1234567890" + type: string + type: object + handlers.CreateSupportedOperationReq: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string + type: object + handlers.CreateTicketOutcomeReq: + properties: + event_id: + description: TicketID int64 `json:"ticket_id" example:"1"` + example: 1 + type: integer + market_id: + example: 1 + type: integer + odd_id: + example: 1 + type: integer + type: object handlers.CreateTicketReq: properties: amount: @@ -123,7 +501,7 @@ definitions: type: number outcomes: items: - type: integer + $ref: '#/definitions/handlers.CreateTicketOutcomeReq' type: array total_odds: example: 4.22 @@ -131,6 +509,9 @@ definitions: type: object handlers.CreateTicketRes: properties: + created_number: + example: 3 + type: integer fast_code: example: 1234 type: integer @@ -145,19 +526,15 @@ definitions: example: 100 type: number bank_code: - description: Payment Details for bank type: string beneficiary_name: type: string bet_id: example: 1 type: integer - branch_id: - example: 1 - type: integer - cashier_id: - example: 1 - type: integer + cashout_id: + example: "191212" + type: string full_name: example: John Smith type: string @@ -170,6 +547,18 @@ definitions: type: string reference_number: type: string + type: + example: 1 + type: integer + type: object + handlers.CreateTransferReq: + properties: + amount: + example: 100 + type: number + payment_method: + example: cash + type: string type: object handlers.CustomerWalletRes: properties: @@ -255,6 +644,23 @@ definitions: phoneNumber: type: string type: object + handlers.SearchUserByNameOrPhoneReq: + properties: + searchString: + type: string + type: object + handlers.SupportedOperationRes: + properties: + description: + example: Betting on sport events + type: string + id: + example: 1 + type: integer + name: + example: SportsBook + type: string + type: object handlers.TicketRes: properties: amount: @@ -265,7 +671,7 @@ definitions: type: integer outcomes: items: - $ref: '#/definitions/domain.Outcome' + $ref: '#/definitions/domain.TicketOutcome' type: array total_odds: example: 4.22 @@ -308,6 +714,42 @@ definitions: type: string reference_number: type: string + type: + example: 1 + type: integer + verified: + example: true + type: boolean + type: object + handlers.TransferWalletRes: + properties: + amount: + example: 100 + type: number + cashier_id: + example: 789 + type: integer + created_at: + example: "2025-04-08T12:00:00Z" + type: string + id: + example: 1 + type: integer + payment_method: + example: bank + type: string + receiver_wallet_id: + example: 1 + type: integer + sender_wallet_id: + example: 1 + type: integer + type: + example: transfer + type: string + updated_at: + example: "2025-04-08T12:30:00Z" + type: string verified: example: true type: boolean @@ -370,6 +812,9 @@ definitions: is_bettable: example: true type: boolean + is_transferable: + example: true + type: boolean is_withdraw: example: true type: boolean @@ -397,6 +842,8 @@ definitions: type: string refresh_token: type: string + role: + type: string type: object handlers.logoutReq: properties: @@ -410,16 +857,32 @@ definitions: refresh_token: type: string type: object + handlers.updateUserReq: + properties: + first_name: + example: John + type: string + last_name: + example: Doe + type: string + suspended: + example: false + type: boolean + type: object response.APIResponse: properties: data: {} message: type: string metadata: {} + page: + type: integer status: $ref: '#/definitions/response.Status' timestamp: type: string + total: + type: integer type: object response.Status: enum: @@ -689,6 +1152,1018 @@ paths: summary: Updates the cashed out field tags: - bet + /bet/cashout/{id}: + get: + consumes: + - application/json + description: Gets a single bet by cashout id + parameters: + - description: cashout ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BetRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets bet by cashout id + tags: + - bet + /branch: + get: + consumes: + - application/json + description: Gets all branches + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets all branches + tags: + - branch + post: + consumes: + - application/json + description: Creates a branch + parameters: + - description: Creates branch + in: body + name: createBranch + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a branch + tags: + - branch + /branch/{id}: + delete: + consumes: + - application/json + description: Delete the branch + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the branch + tags: + - branch + get: + consumes: + - application/json + description: Gets a single branch by id + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchDetailRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branch by id + tags: + - branch + put: + consumes: + - application/json + description: Updates a branch + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + - description: Update Branch + in: body + name: updateBranch + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Updates a branch + tags: + - branch + /branch/{id}/bets: + get: + consumes: + - application/json + description: Gets bets by its branch id + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BetRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets bets by its branch id + tags: + - branch + /branch/{id}/operation: + get: + consumes: + - application/json + description: Gets branch operations + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchOperationRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branch operations + tags: + - branch + /branch/{id}/operation/{opID}: + delete: + consumes: + - application/json + description: Delete the branch operation + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + - description: Branch Operation ID + in: path + name: opID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the branch operation + tags: + - branch + /branchWallet: + get: + consumes: + - application/json + description: Retrieve all branch wallets + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.WalletRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all branch wallets + tags: + - wallet + /cashiers: + get: + consumes: + - application/json + description: Get all cashiers + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all cashiers + tags: + - cashier + post: + consumes: + - application/json + description: Create cashier + parameters: + - description: Create cashier + in: body + name: cashier + required: true + schema: + $ref: '#/definitions/handlers.CreateCashierReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create cashier + tags: + - cashier + /cashiers/{id}: + put: + consumes: + - application/json + description: Update cashier + parameters: + - description: Update cashier + in: body + name: cashier + required: true + schema: + $ref: '#/definitions/handlers.updateUserReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Update cashier + tags: + - cashier + /company: + get: + consumes: + - application/json + description: Gets all companies + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.CompanyRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets all companies + tags: + - company + post: + consumes: + - application/json + description: Creates a company + parameters: + - description: Creates company + in: body + name: createCompany + required: true + schema: + $ref: '#/definitions/handlers.CreateCompanyReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a company + tags: + - company + /company/{id}: + delete: + consumes: + - application/json + description: Delete the company + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Delete the company + tags: + - company + get: + consumes: + - application/json + description: Gets a single company by id + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets company by id + tags: + - company + put: + consumes: + - application/json + description: Updates a company + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + - description: Update Company + in: body + name: updateCompany + required: true + schema: + $ref: '#/definitions/handlers.CreateCompanyReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CompanyRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Updates a company + tags: + - company + /company/{id}/branch: + get: + consumes: + - application/json + description: Gets branches by company id + parameters: + - description: Company ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branches by company id + tags: + - branch + /manager/{id}/branch: + get: + consumes: + - application/json + description: Gets a branches by manager id + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets branches by manager id + tags: + - branch + /managers: + get: + consumes: + - application/json + description: Get all Managers + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all Managers + tags: + - manager + post: + consumes: + - application/json + description: Create Managers + parameters: + - description: Create manager + in: body + name: manger + required: true + schema: + $ref: '#/definitions/handlers.CreateManagerReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create Managers + tags: + - manager + /managers/{id}: + put: + consumes: + - application/json + description: Update Managers + parameters: + - description: Update Managers + in: body + name: Managers + required: true + schema: + $ref: '#/definitions/handlers.updateUserReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Update Managers + tags: + - Managers + /operation: + post: + consumes: + - application/json + description: Creates a operation + parameters: + - description: Creates operation + in: body + name: createBranchOperation + required: true + schema: + $ref: '#/definitions/handlers.CreateBranchOperationReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.BranchOperationRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a operation + tags: + - branch + /prematch/events: + get: + consumes: + - application/json + description: Retrieve all upcoming events from the database + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Page size + in: query + name: page_size + type: integer + - description: League ID Filter + in: query + name: league_id + type: string + - description: Sport ID Filter + in: query + name: sport_id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.UpcomingEvent' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve all upcoming events + tags: + - prematch + /prematch/events/{id}: + get: + consumes: + - application/json + description: Retrieve an upcoming event by ID + parameters: + - description: ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.UpcomingEvent' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve an upcoming by ID + tags: + - prematch + /prematch/odds: + get: + consumes: + - application/json + description: Retrieve all prematch odds from the database + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.Odd' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve all prematch odds + tags: + - prematch + /prematch/odds/{event_id}: + get: + consumes: + - application/json + description: Retrieve prematch odds for a specific event by event ID + parameters: + - description: Event ID + in: path + name: event_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.Odd' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve prematch odds for an event + tags: + - prematch + /prematch/odds/upcoming/{upcoming_id}: + get: + consumes: + - application/json + description: Retrieve prematch odds by upcoming event ID (FI from Bet365) with + optional pagination + parameters: + - description: Upcoming Event ID (FI) + in: path + name: upcoming_id + required: true + type: string + - description: 'Number of results to return (default: 10)' + in: query + name: limit + type: integer + - description: 'Number of results to skip (default: 0)' + in: query + name: offset + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.Odd' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve prematch odds by upcoming ID (FI) + tags: + - prematch + /prematch/odds/upcoming/{upcoming_id}/market/{market_id}: + get: + consumes: + - application/json + description: Retrieve raw odds records using a Market ID + parameters: + - description: Upcoming ID + in: path + name: upcoming_id + required: true + type: string + - description: Market ID + in: path + name: market_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.RawOddsByMarketID' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve raw odds by Market ID + tags: + - prematch + /search/branch: + get: + consumes: + - application/json + description: Search branches by name or location + parameters: + - description: Search query + in: query + name: q + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Search branches + tags: + - branch + /supportedOperation: + get: + consumes: + - application/json + description: Gets all supported operations + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.BranchDetailRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets all supported operations + tags: + - branch + post: + consumes: + - application/json + description: Creates a supported operation + parameters: + - description: Creates supported operation + in: body + name: createSupportedOperation + required: true + schema: + $ref: '#/definitions/handlers.CreateSupportedOperationReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.SupportedOperationRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a supported operation + tags: + - branch /ticket: get: consumes: @@ -889,6 +2364,96 @@ paths: summary: Updates the cashed out field tags: - transaction + /transfer/refill/:id: + post: + consumes: + - application/json + description: Super Admin route to refill a wallet + parameters: + - description: Create Transfer + in: body + name: refillWallet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransferReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransferWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Refill wallet + tags: + - transfer + /transfer/wallet/:id: + post: + consumes: + - application/json + description: Create a transfer to wallet + parameters: + - description: Create Transfer + in: body + name: transferToWallet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransferReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransferWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a transfer to wallet + tags: + - transfer + /transfer/wallet/{id}: + get: + consumes: + - application/json + description: Get transfer by wallet + parameters: + - description: Create Transfer + in: body + name: transferToWallet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransferReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransferWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get transfer by wallet + tags: + - transfer /user/checkPhoneEmailExist: post: consumes: @@ -1004,6 +2569,36 @@ paths: summary: Reset password tags: - user + /user/search: + post: + consumes: + - application/json + description: Search for user using name or phone + parameters: + - description: Search for using his name or phone + in: body + name: searchUserByNameOrPhone + required: true + schema: + $ref: '#/definitions/handlers.SearchUserByNameOrPhoneReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.UserProfileRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Search for user using name or phone + tags: + - user /user/sendRegisterCode: post: consumes: diff --git a/gen/db/auth.sql.go b/gen/db/auth.sql.go index e9f0597..3b5854c 100644 --- a/gen/db/auth.sql.go +++ b/gen/db/auth.sql.go @@ -17,11 +17,11 @@ VALUES ($1, $2, $3, $4, $5) ` type CreateRefreshTokenParams struct { - UserID int64 - Token string - ExpiresAt pgtype.Timestamptz - CreatedAt pgtype.Timestamptz - Revoked bool + UserID int64 `json:"user_id"` + Token string `json:"token"` + ExpiresAt pgtype.Timestamptz `json:"expires_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + Revoked bool `json:"revoked"` } func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) error { @@ -60,8 +60,8 @@ WHERE email = $1 OR phone_number = $2 ` type GetUserByEmailPhoneParams struct { - Email pgtype.Text - PhoneNumber pgtype.Text + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` } func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (User, error) { diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index f3667c6..11a44ad 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -12,20 +12,31 @@ import ( ) const CreateBet = `-- name: CreateBet :one -INSERT INTO bets (amount, total_odds, status, full_name, phone_number, branch_id, user_id, is_shop_bet) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -RETURNING id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, created_at, updated_at, is_shop_bet +INSERT INTO bets ( + amount, + total_odds, + status, + full_name, + phone_number, + branch_id, + user_id, + is_shop_bet, + cashout_id + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet ` type CreateBetParams struct { - Amount int64 - TotalOdds float32 - Status int32 - FullName string - PhoneNumber string - BranchID pgtype.Int8 - UserID pgtype.Int8 - IsShopBet bool + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + IsShopBet bool `json:"is_shop_bet"` + CashoutID string `json:"cashout_id"` } func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) { @@ -38,6 +49,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro arg.BranchID, arg.UserID, arg.IsShopBet, + arg.CashoutID, ) var i Bet err := row.Scan( @@ -50,6 +62,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro &i.BranchID, &i.UserID, &i.CashedOut, + &i.CashoutID, &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, @@ -57,8 +70,24 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro return i, err } +type CreateBetOutcomeParams struct { + BetID int64 `json:"bet_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Expires pgtype.Timestamp `json:"expires"` +} + const DeleteBet = `-- name: DeleteBet :exec -DELETE FROM bets WHERE id = $1 +DELETE FROM bets +WHERE id = $1 ` func (q *Queries) DeleteBet(ctx context.Context, id int64) error { @@ -66,19 +95,30 @@ func (q *Queries) DeleteBet(ctx context.Context, id int64) error { return err } -const GetAllBets = `-- name: GetAllBets :many -SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, created_at, updated_at, is_shop_bet FROM bets +const DeleteBetOutcome = `-- name: DeleteBetOutcome :exec +DELETE FROM bet_outcomes +WHERE bet_id = $1 ` -func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { +func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error { + _, err := q.db.Exec(ctx, DeleteBetOutcome, betID) + return err +} + +const GetAllBets = `-- name: GetAllBets :many +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +` + +func (q *Queries) GetAllBets(ctx context.Context) ([]BetWithOutcome, error) { rows, err := q.db.Query(ctx, GetAllBets) if err != nil { return nil, err } defer rows.Close() - var items []Bet + var items []BetWithOutcome for rows.Next() { - var i Bet + var i BetWithOutcome if err := rows.Scan( &i.ID, &i.Amount, @@ -89,9 +129,11 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { &i.BranchID, &i.UserID, &i.CashedOut, + &i.CashoutID, &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.Outcomes, ); err != nil { return nil, err } @@ -103,13 +145,56 @@ func (q *Queries) GetAllBets(ctx context.Context) ([]Bet, error) { return items, nil } -const GetBetByID = `-- name: GetBetByID :one -SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, created_at, updated_at, is_shop_bet FROM bets WHERE id = $1 +const GetBetByBranchID = `-- name: GetBetByBranchID :many +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE branch_id = $1 ` -func (q *Queries) GetBetByID(ctx context.Context, id int64) (Bet, error) { - row := q.db.QueryRow(ctx, GetBetByID, id) - var i Bet +func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([]BetWithOutcome, error) { + rows, err := q.db.Query(ctx, GetBetByBranchID, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BetWithOutcome + for rows.Next() { + var i BetWithOutcome + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.TotalOdds, + &i.Status, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.UserID, + &i.CashedOut, + &i.CashoutID, + &i.CreatedAt, + &i.UpdatedAt, + &i.IsShopBet, + &i.Outcomes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBetByCashoutID = `-- name: GetBetByCashoutID :one +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE cashout_id = $1 +` + +func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetWithOutcome, error) { + row := q.db.QueryRow(ctx, GetBetByCashoutID, cashoutID) + var i BetWithOutcome err := row.Scan( &i.ID, &i.Amount, @@ -120,23 +205,89 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (Bet, error) { &i.BranchID, &i.UserID, &i.CashedOut, + &i.CashoutID, &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.Outcomes, ) return i, err } +const GetBetByID = `-- name: GetBetByID :one +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE id = $1 +` + +func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, error) { + row := q.db.QueryRow(ctx, GetBetByID, id) + var i BetWithOutcome + err := row.Scan( + &i.ID, + &i.Amount, + &i.TotalOdds, + &i.Status, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.UserID, + &i.CashedOut, + &i.CashoutID, + &i.CreatedAt, + &i.UpdatedAt, + &i.IsShopBet, + &i.Outcomes, + ) + return i, err +} + +const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec +UPDATE bet_outcomes +SET status = $1 +WHERE id = $2 +` + +type UpdateBetOutcomeStatusParams struct { + Status int32 `json:"status"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutcomeStatusParams) error { + _, err := q.db.Exec(ctx, UpdateBetOutcomeStatus, arg.Status, arg.ID) + return err +} + const UpdateCashOut = `-- name: UpdateCashOut :exec -UPDATE bets SET cashed_out = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1 +UPDATE bets +SET cashed_out = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 ` type UpdateCashOutParams struct { - ID int64 - CashedOut pgtype.Bool + ID int64 `json:"id"` + CashedOut bool `json:"cashed_out"` } func (q *Queries) UpdateCashOut(ctx context.Context, arg UpdateCashOutParams) error { _, err := q.db.Exec(ctx, UpdateCashOut, arg.ID, arg.CashedOut) return err } + +const UpdateStatus = `-- name: UpdateStatus :exec +UPDATE bets +SET status = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateStatusParams struct { + ID int64 `json:"id"` + Status int32 `json:"status"` +} + +func (q *Queries) UpdateStatus(ctx context.Context, arg UpdateStatusParams) error { + _, err := q.db.Exec(ctx, UpdateStatus, arg.ID, arg.Status) + return err +} diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go new file mode 100644 index 0000000..a04d4fd --- /dev/null +++ b/gen/db/branch.sql.go @@ -0,0 +1,551 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: branch.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const CreateBranch = `-- name: CreateBranch :one +INSERT INTO branches ( + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at +` + +type CreateBranchParams struct { + Name string `json:"name"` + Location string `json:"location"` + WalletID int64 `json:"wallet_id"` + BranchManagerID int64 `json:"branch_manager_id"` + CompanyID int64 `json:"company_id"` + IsSelfOwned bool `json:"is_self_owned"` +} + +func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Branch, error) { + row := q.db.QueryRow(ctx, CreateBranch, + arg.Name, + arg.Location, + arg.WalletID, + arg.BranchManagerID, + arg.CompanyID, + arg.IsSelfOwned, + ) + var i Branch + err := row.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateBranchCashier = `-- name: CreateBranchCashier :one +INSERT INTO branch_cashiers (user_id, branch_id) +VALUES ($1, $2) +RETURNING id, user_id, branch_id +` + +type CreateBranchCashierParams struct { + UserID int64 `json:"user_id"` + BranchID int64 `json:"branch_id"` +} + +func (q *Queries) CreateBranchCashier(ctx context.Context, arg CreateBranchCashierParams) (BranchCashier, error) { + row := q.db.QueryRow(ctx, CreateBranchCashier, arg.UserID, arg.BranchID) + var i BranchCashier + err := row.Scan(&i.ID, &i.UserID, &i.BranchID) + return i, err +} + +const CreateBranchOperation = `-- name: CreateBranchOperation :one +INSERT INTO branch_operations (operation_id, branch_id) +VALUES ($1, $2) +RETURNING id, operation_id, branch_id, created_at, updated_at +` + +type CreateBranchOperationParams struct { + OperationID int64 `json:"operation_id"` + BranchID int64 `json:"branch_id"` +} + +func (q *Queries) CreateBranchOperation(ctx context.Context, arg CreateBranchOperationParams) (BranchOperation, error) { + row := q.db.QueryRow(ctx, CreateBranchOperation, arg.OperationID, arg.BranchID) + var i BranchOperation + err := row.Scan( + &i.ID, + &i.OperationID, + &i.BranchID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateSupportedOperation = `-- name: CreateSupportedOperation :one +INSERT INTO supported_operations (name, description) +VALUES ($1, $2) +RETURNING id, name, description +` + +type CreateSupportedOperationParams struct { + Name string `json:"name"` + Description string `json:"description"` +} + +func (q *Queries) CreateSupportedOperation(ctx context.Context, arg CreateSupportedOperationParams) (SupportedOperation, error) { + row := q.db.QueryRow(ctx, CreateSupportedOperation, arg.Name, arg.Description) + var i SupportedOperation + err := row.Scan(&i.ID, &i.Name, &i.Description) + return i, err +} + +const DeleteBranch = `-- name: DeleteBranch :exec +DELETE FROM branches +WHERE id = $1 +` + +func (q *Queries) DeleteBranch(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, DeleteBranch, id) + return err +} + +const DeleteBranchCashier = `-- name: DeleteBranchCashier :exec +DELETE FROM branch_cashiers +WHERE user_id = $1 +` + +func (q *Queries) DeleteBranchCashier(ctx context.Context, userID int64) error { + _, err := q.db.Exec(ctx, DeleteBranchCashier, userID) + return err +} + +const DeleteBranchOperation = `-- name: DeleteBranchOperation :exec +DELETE FROM branch_operations +WHERE operation_id = $1 + AND branch_id = $2 +` + +type DeleteBranchOperationParams struct { + OperationID int64 `json:"operation_id"` + BranchID int64 `json:"branch_id"` +} + +func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOperationParams) error { + _, err := q.db.Exec(ctx, DeleteBranchOperation, arg.OperationID, arg.BranchID) + return err +} + +const GetAllBranches = `-- name: GetAllBranches :many +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +` + +func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, GetAllBranches) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BranchDetail + for rows.Next() { + var i BranchDetail + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAllCashiers = `-- name: GetAllCashiers :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +` + +func (q *Queries) GetAllCashiers(ctx context.Context) ([]User, error) { + rows, err := q.db.Query(ctx, GetAllCashiers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.SuspendedAt, + &i.Suspended, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAllSupportedOperations = `-- name: GetAllSupportedOperations :many +SELECT id, name, description +FROM supported_operations +` + +func (q *Queries) GetAllSupportedOperations(ctx context.Context) ([]SupportedOperation, error) { + rows, err := q.db.Query(ctx, GetAllSupportedOperations) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SupportedOperation + for rows.Next() { + var i SupportedOperation + if err := rows.Scan(&i.ID, &i.Name, &i.Description); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBranchByCashier = `-- name: GetBranchByCashier :one +SELECT branches.id, branches.name, branches.location, branches.wallet_id, branches.branch_manager_id, branches.company_id, branches.is_self_owned, branches.created_at, branches.updated_at +FROM branch_cashiers + JOIN branches ON branch_cashiers.branch_id = branches.id +WHERE branch_cashiers.user_id = $1 +` + +func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch, error) { + row := q.db.QueryRow(ctx, GetBranchByCashier, userID) + var i Branch + err := row.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE company_id = $1 +` + +func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, GetBranchByCompanyID, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BranchDetail + for rows.Next() { + var i BranchDetail + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBranchByID = `-- name: GetBranchByID :one +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE id = $1 +` + +func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, error) { + row := q.db.QueryRow(ctx, GetBranchByID, id) + var i BranchDetail + err := row.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ) + return i, err +} + +const GetBranchByManagerID = `-- name: GetBranchByManagerID :many +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE branch_manager_id = $1 +` + +func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, GetBranchByManagerID, branchManagerID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BranchDetail + for rows.Next() { + var i BranchDetail + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetBranchOperations = `-- name: GetBranchOperations :many +SELECT branch_operations.id, branch_operations.operation_id, branch_operations.branch_id, branch_operations.created_at, branch_operations.updated_at, + supported_operations.name, + supported_operations.description +FROM branch_operations + JOIN supported_operations ON branch_operations.operation_id = supported_operations.id +WHERE branch_operations.branch_id = $1 +` + +type GetBranchOperationsRow struct { + ID int64 `json:"id"` + OperationID int64 `json:"operation_id"` + BranchID int64 `json:"branch_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + Name string `json:"name"` + Description string `json:"description"` +} + +func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]GetBranchOperationsRow, error) { + rows, err := q.db.Query(ctx, GetBranchOperations, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetBranchOperationsRow + for rows.Next() { + var i GetBranchOperationsRow + if err := rows.Scan( + &i.ID, + &i.OperationID, + &i.BranchID, + &i.CreatedAt, + &i.UpdatedAt, + &i.Name, + &i.Description, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCashiersByBranch = `-- name: GetCashiersByBranch :many +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended +FROM branch_cashiers + JOIN users ON branch_cashiers.user_id = users.id +WHERE branch_cashiers.branch_id = $1 +` + +func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]User, error) { + rows, err := q.db.Query(ctx, GetCashiersByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.Password, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + &i.SuspendedAt, + &i.Suspended, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const SearchBranchByName = `-- name: SearchBranchByName :many +SELECT id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number +FROM branch_details +WHERE name ILIKE '%' || $1 || '%' +` + +func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, SearchBranchByName, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BranchDetail + for rows.Next() { + var i BranchDetail + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + &i.ManagerName, + &i.ManagerPhoneNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const UpdateBranch = `-- name: UpdateBranch :one +UPDATE branches +SET name = $1, + location = $2, + branch_manager_id = $3, + company_id = $4, + is_self_owned = $5 +WHERE id = $6 +RETURNING id, name, location, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at +` + +type UpdateBranchParams struct { + Name string `json:"name"` + Location string `json:"location"` + BranchManagerID int64 `json:"branch_manager_id"` + CompanyID int64 `json:"company_id"` + IsSelfOwned bool `json:"is_self_owned"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) { + row := q.db.QueryRow(ctx, UpdateBranch, + arg.Name, + arg.Location, + arg.BranchManagerID, + arg.CompanyID, + arg.IsSelfOwned, + arg.ID, + ) + var i Branch + err := row.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.WalletID, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go new file mode 100644 index 0000000..fb83066 --- /dev/null +++ b/gen/db/company.sql.go @@ -0,0 +1,122 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: company.sql + +package dbgen + +import ( + "context" +) + +const CreateCompany = `-- name: CreateCompany :one +INSERT INTO companies ( + name, + admin_id, + wallet_id + ) +VALUES ($1, $2, $3) +RETURNING id, name, admin_id, wallet_id +` + +type CreateCompanyParams struct { + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` +} + +func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (Company, error) { + row := q.db.QueryRow(ctx, CreateCompany, arg.Name, arg.AdminID, arg.WalletID) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} + +const DeleteCompany = `-- name: DeleteCompany :exec +DELETE FROM companies +WHERE id = $1 +` + +func (q *Queries) DeleteCompany(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, DeleteCompany, id) + return err +} + +const GetAllCompanies = `-- name: GetAllCompanies :many +SELECT id, name, admin_id, wallet_id +FROM companies +` + +func (q *Queries) GetAllCompanies(ctx context.Context) ([]Company, error) { + rows, err := q.db.Query(ctx, GetAllCompanies) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Company + for rows.Next() { + var i Company + if err := rows.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCompanyByID = `-- name: GetCompanyByID :one +SELECT id, name, admin_id, wallet_id +FROM companies +WHERE id = $1 +` + +func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (Company, error) { + row := q.db.QueryRow(ctx, GetCompanyByID, id) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} + +const UpdateCompany = `-- name: UpdateCompany :one +UPDATE companies +SET name = $1, + admin_id = $2 +WHERE id = $3 +RETURNING id, name, admin_id, wallet_id +` + +type UpdateCompanyParams struct { + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (Company, error) { + row := q.db.QueryRow(ctx, UpdateCompany, arg.Name, arg.AdminID, arg.ID) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.AdminID, + &i.WalletID, + ) + return i, err +} diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go new file mode 100644 index 0000000..54dbb8b --- /dev/null +++ b/gen/db/copyfrom.go @@ -0,0 +1,96 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: copyfrom.go + +package dbgen + +import ( + "context" +) + +// iteratorForCreateBetOutcome implements pgx.CopyFromSource. +type iteratorForCreateBetOutcome struct { + rows []CreateBetOutcomeParams + skippedFirstNextCall bool +} + +func (r *iteratorForCreateBetOutcome) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].BetID, + r.rows[0].EventID, + r.rows[0].OddID, + r.rows[0].HomeTeamName, + r.rows[0].AwayTeamName, + r.rows[0].MarketID, + r.rows[0].MarketName, + r.rows[0].Odd, + r.rows[0].OddName, + r.rows[0].OddHeader, + r.rows[0].OddHandicap, + r.rows[0].Expires, + }, nil +} + +func (r iteratorForCreateBetOutcome) Err() error { + return nil +} + +func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) { + return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"bet_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "odd_header", "odd_handicap", "expires"}, &iteratorForCreateBetOutcome{rows: arg}) +} + +// iteratorForCreateTicketOutcome implements pgx.CopyFromSource. +type iteratorForCreateTicketOutcome struct { + rows []CreateTicketOutcomeParams + skippedFirstNextCall bool +} + +func (r *iteratorForCreateTicketOutcome) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForCreateTicketOutcome) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].TicketID, + r.rows[0].EventID, + r.rows[0].OddID, + r.rows[0].HomeTeamName, + r.rows[0].AwayTeamName, + r.rows[0].MarketID, + r.rows[0].MarketName, + r.rows[0].Odd, + r.rows[0].OddName, + r.rows[0].OddHeader, + r.rows[0].OddHandicap, + r.rows[0].Expires, + }, nil +} + +func (r iteratorForCreateTicketOutcome) Err() error { + return nil +} + +func (q *Queries) CreateTicketOutcome(ctx context.Context, arg []CreateTicketOutcomeParams) (int64, error) { + return q.db.CopyFrom(ctx, []string{"ticket_outcomes"}, []string{"ticket_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "odd_header", "odd_handicap", "expires"}, &iteratorForCreateTicketOutcome{rows: arg}) +} diff --git a/gen/db/db.go b/gen/db/db.go index 136f20a..d892683 100644 --- a/gen/db/db.go +++ b/gen/db/db.go @@ -15,6 +15,7 @@ type DBTX interface { Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) Query(context.Context, string, ...interface{}) (pgx.Rows, error) QueryRow(context.Context, string, ...interface{}) pgx.Row + CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) } func New(db DBTX) *Queries { diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go new file mode 100644 index 0000000..16fad8f --- /dev/null +++ b/gen/db/events.sql.go @@ -0,0 +1,516 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: events.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE is_live = false + AND status = 'upcoming' +ORDER BY start_time ASC +` + +type GetAllUpcomingEventsRow struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` + IsLive pgtype.Bool `json:"is_live"` + Status pgtype.Text `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` +} + +func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEventsRow, error) { + rows, err := q.db.Query(ctx, GetAllUpcomingEvents) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllUpcomingEventsRow + for rows.Next() { + var i GetAllUpcomingEventsRow + if err := rows.Scan( + &i.ID, + &i.SportID, + &i.MatchName, + &i.HomeTeam, + &i.AwayTeam, + &i.HomeTeamID, + &i.AwayTeamID, + &i.HomeKitImage, + &i.AwayKitImage, + &i.LeagueID, + &i.LeagueName, + &i.LeagueCc, + &i.StartTime, + &i.IsLive, + &i.Status, + &i.FetchedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE is_live = false + AND status = 'upcoming' + AND ( + league_id = $3 + OR $3 IS NULL + ) + AND ( + sport_id = $4 + OR $4 IS NULL + ) +ORDER BY start_time ASC +LIMIT $1 OFFSET $2 +` + +type GetPaginatedUpcomingEventsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` + LeagueID pgtype.Text `json:"league_id"` + SportID pgtype.Text `json:"sport_id"` +} + +type GetPaginatedUpcomingEventsRow struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` + IsLive pgtype.Bool `json:"is_live"` + Status pgtype.Text `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` +} + +func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) { + rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents, + arg.Limit, + arg.Offset, + arg.LeagueID, + arg.SportID, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetPaginatedUpcomingEventsRow + for rows.Next() { + var i GetPaginatedUpcomingEventsRow + if err := rows.Scan( + &i.ID, + &i.SportID, + &i.MatchName, + &i.HomeTeam, + &i.AwayTeam, + &i.HomeTeamID, + &i.AwayTeamID, + &i.HomeKitImage, + &i.AwayKitImage, + &i.LeagueID, + &i.LeagueName, + &i.LeagueCc, + &i.StartTime, + &i.IsLive, + &i.Status, + &i.FetchedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetTotalEvents = `-- name: GetTotalEvents :one +SELECT COUNT(*) +FROM events +WHERE is_live = false + AND status = 'upcoming' + AND ( + league_id = $1 + OR $1 IS NULL + ) + AND ( + sport_id = $2 + OR $2 IS NULL + ) +` + +type GetTotalEventsParams struct { + LeagueID pgtype.Text `json:"league_id"` + SportID pgtype.Text `json:"sport_id"` +} + +func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) { + row := q.db.QueryRow(ctx, GetTotalEvents, arg.LeagueID, arg.SportID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const GetUpcomingByID = `-- name: GetUpcomingByID :one +SELECT id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status, + fetched_at +FROM events +WHERE id = $1 + AND is_live = false + AND status = 'upcoming' +LIMIT 1 +` + +type GetUpcomingByIDRow struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` + IsLive pgtype.Bool `json:"is_live"` + Status pgtype.Text `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` +} + +func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingByIDRow, error) { + row := q.db.QueryRow(ctx, GetUpcomingByID, id) + var i GetUpcomingByIDRow + err := row.Scan( + &i.ID, + &i.SportID, + &i.MatchName, + &i.HomeTeam, + &i.AwayTeam, + &i.HomeTeamID, + &i.AwayTeamID, + &i.HomeKitImage, + &i.AwayKitImage, + &i.LeagueID, + &i.LeagueName, + &i.LeagueCc, + &i.StartTime, + &i.IsLive, + &i.Status, + &i.FetchedAt, + ) + return i, err +} + +const InsertEvent = `-- name: InsertEvent :exec +INSERT INTO events ( + id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + score, + match_minute, + timer_status, + added_time, + match_period, + is_live, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + $14, + $15, + $16, + $17, + $18, + $19, + $20 + ) ON CONFLICT (id) DO +UPDATE +SET sport_id = EXCLUDED.sport_id, + match_name = EXCLUDED.match_name, + home_team = EXCLUDED.home_team, + away_team = EXCLUDED.away_team, + home_team_id = EXCLUDED.home_team_id, + away_team_id = EXCLUDED.away_team_id, + home_kit_image = EXCLUDED.home_kit_image, + away_kit_image = EXCLUDED.away_kit_image, + league_id = EXCLUDED.league_id, + league_name = EXCLUDED.league_name, + league_cc = EXCLUDED.league_cc, + start_time = EXCLUDED.start_time, + score = EXCLUDED.score, + match_minute = EXCLUDED.match_minute, + timer_status = EXCLUDED.timer_status, + added_time = EXCLUDED.added_time, + match_period = EXCLUDED.match_period, + is_live = EXCLUDED.is_live, + status = EXCLUDED.status, + fetched_at = now() +` + +type InsertEventParams struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` + Score pgtype.Text `json:"score"` + MatchMinute pgtype.Int4 `json:"match_minute"` + TimerStatus pgtype.Text `json:"timer_status"` + AddedTime pgtype.Int4 `json:"added_time"` + MatchPeriod pgtype.Int4 `json:"match_period"` + IsLive pgtype.Bool `json:"is_live"` + Status pgtype.Text `json:"status"` +} + +func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error { + _, err := q.db.Exec(ctx, InsertEvent, + arg.ID, + arg.SportID, + arg.MatchName, + arg.HomeTeam, + arg.AwayTeam, + arg.HomeTeamID, + arg.AwayTeamID, + arg.HomeKitImage, + arg.AwayKitImage, + arg.LeagueID, + arg.LeagueName, + arg.LeagueCc, + arg.StartTime, + arg.Score, + arg.MatchMinute, + arg.TimerStatus, + arg.AddedTime, + arg.MatchPeriod, + arg.IsLive, + arg.Status, + ) + return err +} + +const InsertUpcomingEvent = `-- name: InsertUpcomingEvent :exec +INSERT INTO events ( + id, + sport_id, + match_name, + home_team, + away_team, + home_team_id, + away_team_id, + home_kit_image, + away_kit_image, + league_id, + league_name, + league_cc, + start_time, + is_live, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + false, + 'upcoming' + ) ON CONFLICT (id) DO +UPDATE +SET sport_id = EXCLUDED.sport_id, + match_name = EXCLUDED.match_name, + home_team = EXCLUDED.home_team, + away_team = EXCLUDED.away_team, + home_team_id = EXCLUDED.home_team_id, + away_team_id = EXCLUDED.away_team_id, + home_kit_image = EXCLUDED.home_kit_image, + away_kit_image = EXCLUDED.away_kit_image, + league_id = EXCLUDED.league_id, + league_name = EXCLUDED.league_name, + league_cc = EXCLUDED.league_cc, + start_time = EXCLUDED.start_time, + is_live = false, + status = 'upcoming', + fetched_at = now() +` + +type InsertUpcomingEventParams struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` +} + +func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEventParams) error { + _, err := q.db.Exec(ctx, InsertUpcomingEvent, + arg.ID, + arg.SportID, + arg.MatchName, + arg.HomeTeam, + arg.AwayTeam, + arg.HomeTeamID, + arg.AwayTeamID, + arg.HomeKitImage, + arg.AwayKitImage, + arg.LeagueID, + arg.LeagueName, + arg.LeagueCc, + arg.StartTime, + ) + return err +} + +const ListLiveEvents = `-- name: ListLiveEvents :many +SELECT id +FROM events +WHERE is_live = true +` + +func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) { + rows, err := q.db.Query(ctx, ListLiveEvents) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/gen/db/models.go b/gen/db/models.go index 37cb284..a5df104 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -56,57 +56,208 @@ func (ns NullReferralstatus) Value() (driver.Value, error) { } type Bet struct { - ID int64 - Amount int64 - TotalOdds float32 - Status int32 - FullName string - PhoneNumber string - BranchID pgtype.Int8 - UserID pgtype.Int8 - CashedOut pgtype.Bool - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp - IsShopBet bool + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + CashedOut bool `json:"cashed_out"` + CashoutID string `json:"cashout_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + IsShopBet bool `json:"is_shop_bet"` +} + +type BetOutcome struct { + ID int64 `json:"id"` + BetID int64 `json:"bet_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Status int32 `json:"status"` + Expires pgtype.Timestamp `json:"expires"` +} + +type BetWithOutcome struct { + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + CashedOut bool `json:"cashed_out"` + CashoutID string `json:"cashout_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + IsShopBet bool `json:"is_shop_bet"` + Outcomes []BetOutcome `json:"outcomes"` +} + +type Branch struct { + ID int64 `json:"id"` + Name string `json:"name"` + Location string `json:"location"` + WalletID int64 `json:"wallet_id"` + BranchManagerID int64 `json:"branch_manager_id"` + CompanyID int64 `json:"company_id"` + IsSelfOwned bool `json:"is_self_owned"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type BranchCashier struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + BranchID int64 `json:"branch_id"` +} + +type BranchDetail struct { + ID int64 `json:"id"` + Name string `json:"name"` + Location string `json:"location"` + WalletID int64 `json:"wallet_id"` + BranchManagerID int64 `json:"branch_manager_id"` + CompanyID int64 `json:"company_id"` + IsSelfOwned bool `json:"is_self_owned"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + ManagerName interface{} `json:"manager_name"` + ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"` +} + +type BranchOperation struct { + ID int64 `json:"id"` + OperationID int64 `json:"operation_id"` + BranchID int64 `json:"branch_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type Company struct { + ID int64 `json:"id"` + Name string `json:"name"` + AdminID int64 `json:"admin_id"` + WalletID int64 `json:"wallet_id"` } type CustomerWallet struct { - ID int64 - CustomerID int64 - CompanyID int64 - RegularWalletID int64 - StaticWalletID int64 - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp + ID int64 `json:"id"` + CustomerID int64 `json:"customer_id"` + CompanyID int64 `json:"company_id"` + RegularWalletID int64 `json:"regular_wallet_id"` + StaticWalletID int64 `json:"static_wallet_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type Event struct { + ID string `json:"id"` + SportID pgtype.Text `json:"sport_id"` + MatchName pgtype.Text `json:"match_name"` + HomeTeam pgtype.Text `json:"home_team"` + AwayTeam pgtype.Text `json:"away_team"` + HomeTeamID pgtype.Text `json:"home_team_id"` + AwayTeamID pgtype.Text `json:"away_team_id"` + HomeKitImage pgtype.Text `json:"home_kit_image"` + AwayKitImage pgtype.Text `json:"away_kit_image"` + LeagueID pgtype.Text `json:"league_id"` + LeagueName pgtype.Text `json:"league_name"` + LeagueCc pgtype.Text `json:"league_cc"` + StartTime pgtype.Timestamp `json:"start_time"` + Score pgtype.Text `json:"score"` + MatchMinute pgtype.Int4 `json:"match_minute"` + TimerStatus pgtype.Text `json:"timer_status"` + AddedTime pgtype.Int4 `json:"added_time"` + MatchPeriod pgtype.Int4 `json:"match_period"` + IsLive pgtype.Bool `json:"is_live"` + Status pgtype.Text `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` } type Notification struct { - ID string - RecipientID int64 - Type string - Level string - ErrorSeverity pgtype.Text - Reciever string - IsRead bool - DeliveryStatus string - DeliveryChannel pgtype.Text - Payload []byte - Priority pgtype.Int4 - Version int32 - Timestamp pgtype.Timestamptz - Metadata []byte + ID string `json:"id"` + RecipientID int64 `json:"recipient_id"` + Type string `json:"type"` + Level string `json:"level"` + ErrorSeverity pgtype.Text `json:"error_severity"` + Reciever string `json:"reciever"` + IsRead bool `json:"is_read"` + DeliveryStatus string `json:"delivery_status"` + DeliveryChannel pgtype.Text `json:"delivery_channel"` + Payload []byte `json:"payload"` + Priority pgtype.Int4 `json:"priority"` + Version int32 `json:"version"` + Timestamp pgtype.Timestamptz `json:"timestamp"` + Metadata []byte `json:"metadata"` +} + +type Odd struct { + ID int32 `json:"id"` + EventID pgtype.Text `json:"event_id"` + Fi pgtype.Text `json:"fi"` + MarketType string `json:"market_type"` + MarketName pgtype.Text `json:"market_name"` + MarketCategory pgtype.Text `json:"market_category"` + MarketID pgtype.Text `json:"market_id"` + Name pgtype.Text `json:"name"` + Handicap pgtype.Text `json:"handicap"` + OddsValue pgtype.Float8 `json:"odds_value"` + Section string `json:"section"` + Category pgtype.Text `json:"category"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source pgtype.Text `json:"source"` + IsActive pgtype.Bool `json:"is_active"` } type Otp struct { - ID int64 - SentTo string - Medium string - OtpFor string - Otp string - Used bool - UsedAt pgtype.Timestamptz - CreatedAt pgtype.Timestamptz - ExpiresAt pgtype.Timestamptz +ID int64 `json:"id"` + SentTo string `json:"sent_to"` + Medium string `json:"medium"` + OtpFor string `json:"otp_for"` + Otp string `json:"otp"` + Used bool `json:"used"` + UsedAt pgtype.Timestamptz `json:"used_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + ExpiresAt pgtype.Timestamptz `json:"expires_at"` +} + +type Referral struct { + ID int64 + ReferralCode string + ReferrerID string + ReferredID pgtype.Text + Status Referralstatus + RewardAmount pgtype.Numeric + CashbackAmount pgtype.Numeric + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + ExpiresAt pgtype.Timestamptz +} + +type ReferralSetting struct { + ID int64 + ReferralRewardAmount pgtype.Numeric + CashbackPercentage pgtype.Numeric + BetReferralBonusPercentage pgtype.Numeric + MaxReferrals int32 + ExpiresAfterDays int32 + UpdatedBy string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + Version int32 } type Referral struct { @@ -136,55 +287,88 @@ type ReferralSetting struct { } type RefreshToken struct { - ID int64 - UserID int64 - Token string - ExpiresAt pgtype.Timestamptz - CreatedAt pgtype.Timestamptz - Revoked bool + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Token string `json:"token"` + ExpiresAt pgtype.Timestamptz `json:"expires_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + Revoked bool `json:"revoked"` +} + +type SupportedOperation struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` } type Ticket struct { - ID int64 - Amount pgtype.Int8 - TotalOdds float32 - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type TicketOutcome struct { + ID int64 `json:"id"` + TicketID int64 `json:"ticket_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Status int32 `json:"status"` + Expires pgtype.Timestamp `json:"expires"` +} + +type TicketWithOutcome struct { + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + Outcomes []TicketOutcome `json:"outcomes"` } type Transaction struct { - ID int64 - Amount int64 - BranchID int64 - CashierID int64 - BetID int64 - PaymentOption int64 - FullName string - PhoneNumber string - BankCode string - BeneficiaryName string - AccountName string - AccountNumber string - ReferenceNumber string - Verified bool - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp + ID int64 `json:"id"` + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CashierID int64 `json:"cashier_id"` + BetID int64 `json:"bet_id"` + Type int64 `json:"type"` + PaymentOption int64 `json:"payment_option"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + Verified bool `json:"verified"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } type User struct { - ID int64 - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - Password []byte - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz - SuspendedAt pgtype.Timestamptz - Suspended bool + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + Password []byte `json:"password"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + SuspendedAt pgtype.Timestamptz `json:"suspended_at"` + Suspended bool `json:"suspended"` ReferralCode pgtype.Text ReferredBy pgtype.Text } @@ -216,24 +400,28 @@ type VirtualGameTransaction struct { } type Wallet struct { - ID int64 - Balance int64 - IsWithdraw bool - IsBettable bool - UserID int64 - IsActive bool - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp + ID int64 `json:"id"` + Balance int64 `json:"balance"` + IsWithdraw bool `json:"is_withdraw"` + IsBettable bool `json:"is_bettable"` + IsTransferable bool `json:"is_transferable"` + UserID int64 `json:"user_id"` + IsActive bool `json:"is_active"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` BonusBalance pgtype.Numeric CashBalance pgtype.Numeric } type WalletTransfer struct { - ID int64 - Amount int64 - WalletTransfer string - WalletID int64 - Verified bool - CreatedAt pgtype.Timestamp - UpdatedAt pgtype.Timestamp + ID int64 `json:"id"` + Amount int64 `json:"amount"` + Type string `json:"type"` + ReceiverWalletID int64 `json:"receiver_wallet_id"` + SenderWalletID pgtype.Int8 `json:"sender_wallet_id"` + CashierID pgtype.Int8 `json:"cashier_id"` + Verified bool `json:"verified"` + PaymentMethod string `json:"payment_method"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } diff --git a/gen/db/notification.sql.go b/gen/db/notification.sql.go index 3735d72..5bfedd6 100644 --- a/gen/db/notification.sql.go +++ b/gen/db/notification.sql.go @@ -20,19 +20,19 @@ INSERT INTO notifications ( ` type CreateNotificationParams struct { - ID string - RecipientID int64 - Type string - Level string - ErrorSeverity pgtype.Text - Reciever string - IsRead bool - DeliveryStatus string - DeliveryChannel pgtype.Text - Payload []byte - Priority pgtype.Int4 - Timestamp pgtype.Timestamptz - Metadata []byte + ID string `json:"id"` + RecipientID int64 `json:"recipient_id"` + Type string `json:"type"` + Level string `json:"level"` + ErrorSeverity pgtype.Text `json:"error_severity"` + Reciever string `json:"reciever"` + IsRead bool `json:"is_read"` + DeliveryStatus string `json:"delivery_status"` + DeliveryChannel pgtype.Text `json:"delivery_channel"` + Payload []byte `json:"payload"` + Priority pgtype.Int4 `json:"priority"` + Timestamp pgtype.Timestamptz `json:"timestamp"` + Metadata []byte `json:"metadata"` } func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) (Notification, error) { @@ -141,9 +141,9 @@ SELECT id, recipient_id, type, level, error_severity, reciever, is_read, deliver ` type ListNotificationsParams struct { - RecipientID int64 - Limit int32 - Offset int32 + RecipientID int64 `json:"recipient_id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsParams) ([]Notification, error) { @@ -210,10 +210,10 @@ UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE ` type UpdateNotificationStatusParams struct { - ID string - DeliveryStatus string - IsRead bool - Metadata []byte + ID string `json:"id"` + DeliveryStatus string `json:"delivery_status"` + IsRead bool `json:"is_read"` + Metadata []byte `json:"metadata"` } func (q *Queries) UpdateNotificationStatus(ctx context.Context, arg UpdateNotificationStatusParams) (Notification, error) { diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go new file mode 100644 index 0000000..3f920f4 --- /dev/null +++ b/gen/db/odds.sql.go @@ -0,0 +1,375 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: odds.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many +SELECT event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + fetched_at, + source, + is_active +FROM odds +WHERE is_active = true + AND source = 'b365api' +` + +type GetALLPrematchOddsRow struct { + EventID pgtype.Text `json:"event_id"` + Fi pgtype.Text `json:"fi"` + MarketType string `json:"market_type"` + MarketName pgtype.Text `json:"market_name"` + MarketCategory pgtype.Text `json:"market_category"` + MarketID pgtype.Text `json:"market_id"` + Name pgtype.Text `json:"name"` + Handicap pgtype.Text `json:"handicap"` + OddsValue pgtype.Float8 `json:"odds_value"` + Section string `json:"section"` + Category pgtype.Text `json:"category"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source pgtype.Text `json:"source"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsRow, error) { + rows, err := q.db.Query(ctx, GetALLPrematchOdds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetALLPrematchOddsRow + for rows.Next() { + var i GetALLPrematchOddsRow + if err := rows.Scan( + &i.EventID, + &i.Fi, + &i.MarketType, + &i.MarketName, + &i.MarketCategory, + &i.MarketID, + &i.Name, + &i.Handicap, + &i.OddsValue, + &i.Section, + &i.Category, + &i.RawOdds, + &i.FetchedAt, + &i.Source, + &i.IsActive, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetPrematchOdds = `-- name: GetPrematchOdds :many +SELECT event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + fetched_at, + source, + is_active +FROM odds +WHERE is_active = true + AND source = 'b365api' +` + +type GetPrematchOddsRow struct { + EventID pgtype.Text `json:"event_id"` + Fi pgtype.Text `json:"fi"` + MarketType string `json:"market_type"` + MarketName pgtype.Text `json:"market_name"` + MarketCategory pgtype.Text `json:"market_category"` + MarketID pgtype.Text `json:"market_id"` + Name pgtype.Text `json:"name"` + Handicap pgtype.Text `json:"handicap"` + OddsValue pgtype.Float8 `json:"odds_value"` + Section string `json:"section"` + Category pgtype.Text `json:"category"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source pgtype.Text `json:"source"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, error) { + rows, err := q.db.Query(ctx, GetPrematchOdds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetPrematchOddsRow + for rows.Next() { + var i GetPrematchOddsRow + if err := rows.Scan( + &i.EventID, + &i.Fi, + &i.MarketType, + &i.MarketName, + &i.MarketCategory, + &i.MarketID, + &i.Name, + &i.Handicap, + &i.OddsValue, + &i.Section, + &i.Category, + &i.RawOdds, + &i.FetchedAt, + &i.Source, + &i.IsActive, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many +SELECT o.event_id, + o.fi, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.name, + o.handicap, + o.odds_value, + o.section, + o.category, + o.raw_odds, + o.fetched_at, + o.source, + o.is_active +FROM odds o + JOIN events e ON o.fi = e.id +WHERE e.id = $1 + AND e.is_live = false + AND e.status = 'upcoming' + AND o.is_active = true + AND o.source = 'b365api' +LIMIT $2 OFFSET $3 +` + +type GetPrematchOddsByUpcomingIDParams struct { + ID string `json:"id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +type GetPrematchOddsByUpcomingIDRow struct { + EventID pgtype.Text `json:"event_id"` + Fi pgtype.Text `json:"fi"` + MarketType string `json:"market_type"` + MarketName pgtype.Text `json:"market_name"` + MarketCategory pgtype.Text `json:"market_category"` + MarketID pgtype.Text `json:"market_id"` + Name pgtype.Text `json:"name"` + Handicap pgtype.Text `json:"handicap"` + OddsValue pgtype.Float8 `json:"odds_value"` + Section string `json:"section"` + Category pgtype.Text `json:"category"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source pgtype.Text `json:"source"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPrematchOddsByUpcomingIDParams) ([]GetPrematchOddsByUpcomingIDRow, error) { + rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, arg.ID, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetPrematchOddsByUpcomingIDRow + for rows.Next() { + var i GetPrematchOddsByUpcomingIDRow + if err := rows.Scan( + &i.EventID, + &i.Fi, + &i.MarketType, + &i.MarketName, + &i.MarketCategory, + &i.MarketID, + &i.Name, + &i.Handicap, + &i.OddsValue, + &i.Section, + &i.Category, + &i.RawOdds, + &i.FetchedAt, + &i.Source, + &i.IsActive, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetRawOddsByMarketID = `-- name: GetRawOddsByMarketID :one +SELECT id, + market_name, + handicap, + raw_odds, + fetched_at +FROM odds +WHERE market_id = $1 + AND fi = $2 + AND is_active = true + AND source = 'b365api' +` + +type GetRawOddsByMarketIDParams struct { + MarketID pgtype.Text `json:"market_id"` + Fi pgtype.Text `json:"fi"` +} + +type GetRawOddsByMarketIDRow struct { + ID int32 `json:"id"` + MarketName pgtype.Text `json:"market_name"` + Handicap pgtype.Text `json:"handicap"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` +} + +func (q *Queries) GetRawOddsByMarketID(ctx context.Context, arg GetRawOddsByMarketIDParams) (GetRawOddsByMarketIDRow, error) { + row := q.db.QueryRow(ctx, GetRawOddsByMarketID, arg.MarketID, arg.Fi) + var i GetRawOddsByMarketIDRow + err := row.Scan( + &i.ID, + &i.MarketName, + &i.Handicap, + &i.RawOdds, + &i.FetchedAt, + ) + return i, err +} + +const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec +INSERT INTO odds ( + event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + is_active, + source, + fetched_at + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + $14, + $15 + ) ON CONFLICT (event_id, market_id) DO +UPDATE +SET odds_value = EXCLUDED.odds_value, + raw_odds = EXCLUDED.raw_odds, + market_type = EXCLUDED.market_type, + market_name = EXCLUDED.market_name, + market_category = EXCLUDED.market_category, + name = EXCLUDED.name, + handicap = EXCLUDED.handicap, + fetched_at = EXCLUDED.fetched_at, + is_active = EXCLUDED.is_active, + source = EXCLUDED.source, + fi = EXCLUDED.fi +` + +type InsertNonLiveOddParams struct { + EventID pgtype.Text `json:"event_id"` + Fi pgtype.Text `json:"fi"` + MarketType string `json:"market_type"` + MarketName pgtype.Text `json:"market_name"` + MarketCategory pgtype.Text `json:"market_category"` + MarketID pgtype.Text `json:"market_id"` + Name pgtype.Text `json:"name"` + Handicap pgtype.Text `json:"handicap"` + OddsValue pgtype.Float8 `json:"odds_value"` + Section string `json:"section"` + Category pgtype.Text `json:"category"` + RawOdds []byte `json:"raw_odds"` + IsActive pgtype.Bool `json:"is_active"` + Source pgtype.Text `json:"source"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` +} + +func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddParams) error { + _, err := q.db.Exec(ctx, InsertNonLiveOdd, + arg.EventID, + arg.Fi, + arg.MarketType, + arg.MarketName, + arg.MarketCategory, + arg.MarketID, + arg.Name, + arg.Handicap, + arg.OddsValue, + arg.Section, + arg.Category, + arg.RawOdds, + arg.IsActive, + arg.Source, + arg.FetchedAt, + ) + return err +} diff --git a/gen/db/otp.sql.go b/gen/db/otp.sql.go index e0b9806..99cdd4c 100644 --- a/gen/db/otp.sql.go +++ b/gen/db/otp.sql.go @@ -17,12 +17,12 @@ VALUES ($1, $2, $3, $4, FALSE, $5, $6) ` type CreateOtpParams struct { - SentTo string - Medium string - OtpFor string - Otp string - CreatedAt pgtype.Timestamptz - ExpiresAt pgtype.Timestamptz + SentTo string `json:"sent_to"` + Medium string `json:"medium"` + OtpFor string `json:"otp_for"` + Otp string `json:"otp"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + ExpiresAt pgtype.Timestamptz `json:"expires_at"` } func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error { @@ -45,9 +45,9 @@ ORDER BY created_at DESC LIMIT 1 ` type GetOtpParams struct { - SentTo string - OtpFor string - Medium string + SentTo string `json:"sent_to"` + OtpFor string `json:"otp_for"` + Medium string `json:"medium"` } func (q *Queries) GetOtp(ctx context.Context, arg GetOtpParams) (Otp, error) { @@ -74,8 +74,8 @@ WHERE id = $1 ` type MarkOtpAsUsedParams struct { - ID int64 - UsedAt pgtype.Timestamptz + ID int64 `json:"id"` + UsedAt pgtype.Timestamptz `json:"used_at"` } func (q *Queries) MarkOtpAsUsed(ctx context.Context, arg MarkOtpAsUsedParams) error { diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index d7e5ff3..054372d 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -18,8 +18,8 @@ RETURNING id, amount, total_odds, created_at, updated_at ` type CreateTicketParams struct { - Amount pgtype.Int8 - TotalOdds float32 + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` } func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) { @@ -35,8 +35,24 @@ func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Tic return i, err } +type CreateTicketOutcomeParams struct { + TicketID int64 `json:"ticket_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Expires pgtype.Timestamp `json:"expires"` +} + const DeleteOldTickets = `-- name: DeleteOldTickets :exec -Delete from tickets where created_at < now() - interval '1 day' +Delete from tickets +where created_at < now() - interval '1 day' ` func (q *Queries) DeleteOldTickets(ctx context.Context) error { @@ -45,7 +61,8 @@ func (q *Queries) DeleteOldTickets(ctx context.Context) error { } const DeleteTicket = `-- name: DeleteTicket :exec -DELETE FROM tickets WHERE id = $1 +DELETE FROM tickets +WHERE id = $1 ` func (q *Queries) DeleteTicket(ctx context.Context, id int64) error { @@ -53,25 +70,37 @@ func (q *Queries) DeleteTicket(ctx context.Context, id int64) error { return err } -const GetAllTickets = `-- name: GetAllTickets :many -SELECT id, amount, total_odds, created_at, updated_at FROM tickets +const DeleteTicketOutcome = `-- name: DeleteTicketOutcome :exec +Delete from ticket_outcomes +where ticket_id = $1 ` -func (q *Queries) GetAllTickets(ctx context.Context) ([]Ticket, error) { +func (q *Queries) DeleteTicketOutcome(ctx context.Context, ticketID int64) error { + _, err := q.db.Exec(ctx, DeleteTicketOutcome, ticketID) + return err +} + +const GetAllTickets = `-- name: GetAllTickets :many +SELECT id, amount, total_odds, created_at, updated_at, outcomes +FROM ticket_with_outcomes +` + +func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error) { rows, err := q.db.Query(ctx, GetAllTickets) if err != nil { return nil, err } defer rows.Close() - var items []Ticket + var items []TicketWithOutcome for rows.Next() { - var i Ticket + var i TicketWithOutcome if err := rows.Scan( &i.ID, &i.Amount, &i.TotalOdds, &i.CreatedAt, &i.UpdatedAt, + &i.Outcomes, ); err != nil { return nil, err } @@ -84,18 +113,78 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]Ticket, error) { } const GetTicketByID = `-- name: GetTicketByID :one -SELECT id, amount, total_odds, created_at, updated_at FROM tickets WHERE id = $1 +SELECT id, amount, total_odds, created_at, updated_at, outcomes +FROM ticket_with_outcomes +WHERE id = $1 ` -func (q *Queries) GetTicketByID(ctx context.Context, id int64) (Ticket, error) { +func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcome, error) { row := q.db.QueryRow(ctx, GetTicketByID, id) - var i Ticket + var i TicketWithOutcome err := row.Scan( &i.ID, &i.Amount, &i.TotalOdds, &i.CreatedAt, &i.UpdatedAt, + &i.Outcomes, ) return i, err } + +const GetTicketOutcome = `-- name: GetTicketOutcome :many +SELECT id, ticket_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires +FROM ticket_outcomes +WHERE ticket_id = $1 +` + +func (q *Queries) GetTicketOutcome(ctx context.Context, ticketID int64) ([]TicketOutcome, error) { + rows, err := q.db.Query(ctx, GetTicketOutcome, ticketID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TicketOutcome + for rows.Next() { + var i TicketOutcome + if err := rows.Scan( + &i.ID, + &i.TicketID, + &i.EventID, + &i.OddID, + &i.HomeTeamName, + &i.AwayTeamName, + &i.MarketID, + &i.MarketName, + &i.Odd, + &i.OddName, + &i.OddHeader, + &i.OddHandicap, + &i.Status, + &i.Expires, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const UpdateTicketOutcomeStatus = `-- name: UpdateTicketOutcomeStatus :exec +UPDATE ticket_outcomes +SET status = $1 +WHERE id = $2 +` + +type UpdateTicketOutcomeStatusParams struct { + Status int32 `json:"status"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateTicketOutcomeStatus(ctx context.Context, arg UpdateTicketOutcomeStatusParams) error { + _, err := q.db.Exec(ctx, UpdateTicketOutcomeStatus, arg.Status, arg.ID) + return err +} diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go index 31b535c..2865972 100644 --- a/gen/db/transactions.sql.go +++ b/gen/db/transactions.sql.go @@ -10,22 +10,23 @@ import ( ) const CreateTransaction = `-- name: CreateTransaction :one -INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at +INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at ` type CreateTransactionParams struct { - Amount int64 - BranchID int64 - CashierID int64 - BetID int64 - PaymentOption int64 - FullName string - PhoneNumber string - BankCode string - BeneficiaryName string - AccountName string - AccountNumber string - ReferenceNumber string + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CashierID int64 `json:"cashier_id"` + BetID int64 `json:"bet_id"` + Type int64 `json:"type"` + PaymentOption int64 `json:"payment_option"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` } func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { @@ -34,6 +35,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa arg.BranchID, arg.CashierID, arg.BetID, + arg.Type, arg.PaymentOption, arg.FullName, arg.PhoneNumber, @@ -50,6 +52,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa &i.BranchID, &i.CashierID, &i.BetID, + &i.Type, &i.PaymentOption, &i.FullName, &i.PhoneNumber, @@ -66,7 +69,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa } const GetAllTransactions = `-- name: GetAllTransactions :many -SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions +SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions ` func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) { @@ -84,6 +87,49 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) &i.BranchID, &i.CashierID, &i.BetID, + &i.Type, + &i.PaymentOption, + &i.FullName, + &i.PhoneNumber, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetTransactionByBranch = `-- name: GetTransactionByBranch :many +SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE branch_id = $1 +` + +func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) { + rows, err := q.db.Query(ctx, GetTransactionByBranch, branchID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.BranchID, + &i.CashierID, + &i.BetID, + &i.Type, &i.PaymentOption, &i.FullName, &i.PhoneNumber, @@ -107,7 +153,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) } const GetTransactionByID = `-- name: GetTransactionByID :one -SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 +SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 ` func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { @@ -119,6 +165,7 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction &i.BranchID, &i.CashierID, &i.BetID, + &i.Type, &i.PaymentOption, &i.FullName, &i.PhoneNumber, @@ -139,8 +186,8 @@ UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = ` type UpdateTransactionVerifiedParams struct { - ID int64 - Verified bool + ID int64 `json:"id"` + Verified bool `json:"verified"` } func (q *Queries) UpdateTransactionVerified(ctx context.Context, arg UpdateTransactionVerifiedParams) error { diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index c29cec8..f4d8cc2 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -7,27 +7,44 @@ package dbgen import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) const CreateTransfer = `-- name: CreateTransfer :one -INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at +INSERT INTO wallet_transfer (amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at ` type CreateTransferParams struct { - Amount int64 - WalletTransfer string - WalletID int64 + Amount int64 `json:"amount"` + Type string `json:"type"` + ReceiverWalletID int64 `json:"receiver_wallet_id"` + SenderWalletID pgtype.Int8 `json:"sender_wallet_id"` + CashierID pgtype.Int8 `json:"cashier_id"` + Verified bool `json:"verified"` + PaymentMethod string `json:"payment_method"` } func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) { - row := q.db.QueryRow(ctx, CreateTransfer, arg.Amount, arg.WalletTransfer, arg.WalletID) + row := q.db.QueryRow(ctx, CreateTransfer, + arg.Amount, + arg.Type, + arg.ReceiverWalletID, + arg.SenderWalletID, + arg.CashierID, + arg.Verified, + arg.PaymentMethod, + ) var i WalletTransfer err := row.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, + &i.PaymentMethod, &i.CreatedAt, &i.UpdatedAt, ) @@ -35,7 +52,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) } const GetAllTransfers = `-- name: GetAllTransfers :many -SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer ` func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) { @@ -50,9 +67,12 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) if err := rows.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, + &i.PaymentMethod, &i.CreatedAt, &i.UpdatedAt, ); err != nil { @@ -67,7 +87,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) } const GetTransferByID = `-- name: GetTransferByID :one -SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer WHERE id = $1 +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE id = $1 ` func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer, error) { @@ -76,9 +96,12 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer err := row.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, + &i.PaymentMethod, &i.CreatedAt, &i.UpdatedAt, ) @@ -86,11 +109,11 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer } const GetTransfersByWallet = `-- name: GetTransfersByWallet :many -SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer WHERE wallet_id = $1 +SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, payment_method, created_at, updated_at FROM wallet_transfer WHERE receiver_wallet_id = $1 OR sender_wallet_id = $1 ` -func (q *Queries) GetTransfersByWallet(ctx context.Context, walletID int64) ([]WalletTransfer, error) { - rows, err := q.db.Query(ctx, GetTransfersByWallet, walletID) +func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID int64) ([]WalletTransfer, error) { + rows, err := q.db.Query(ctx, GetTransfersByWallet, receiverWalletID) if err != nil { return nil, err } @@ -101,9 +124,12 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, walletID int64) ([]W if err := rows.Scan( &i.ID, &i.Amount, - &i.WalletTransfer, - &i.WalletID, + &i.Type, + &i.ReceiverWalletID, + &i.SenderWalletID, + &i.CashierID, &i.Verified, + &i.PaymentMethod, &i.CreatedAt, &i.UpdatedAt, ); err != nil { @@ -122,8 +148,8 @@ UPDATE wallet_transfer SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE i ` type UpdateTransferVerificationParams struct { - Verified bool - ID int64 + Verified bool `json:"verified"` + ID int64 `json:"id"` } func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error { diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index b725021..75dbd5e 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -12,19 +12,28 @@ import ( ) const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one -SELECT - EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists, - EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists +SELECT EXISTS ( + SELECT 1 + FROM users + WHERE users.phone_number = $1 + AND users.phone_number IS NOT NULL + ) AS phone_exists, + EXISTS ( + SELECT 1 + FROM users + WHERE users.email = $2 + AND users.email IS NOT NULL + ) AS email_exists ` type CheckPhoneEmailExistParams struct { - PhoneNumber pgtype.Text - Email pgtype.Text + PhoneNumber pgtype.Text `json:"phone_number"` + Email pgtype.Text `json:"email"` } type CheckPhoneEmailExistRow struct { - PhoneExists bool - EmailExists bool + PhoneExists bool `json:"phone_exists"` + EmailExists bool `json:"email_exists"` } func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailExistParams) (CheckPhoneEmailExistRow, error) { @@ -35,36 +44,55 @@ func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailE } const CreateUser = `-- name: CreateUser :one - -INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at) +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + role, + password, + email_verified, + phone_verified, + created_at, + updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) -RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +RETURNING id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at ` type CreateUserParams struct { - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - Password []byte - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + Password []byte `json:"password"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } type CreateUserRow struct { - ID int64 - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) { @@ -107,21 +135,30 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error { } const GetAllUsers = `-- name: GetAllUsers :many -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users ` type GetAllUsersRow struct { - ID int64 - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) { @@ -156,22 +193,31 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) { } const GetUserByEmail = `-- name: GetUserByEmail :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE email = $1 ` type GetUserByEmailRow struct { - ID int64 - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error) { @@ -222,22 +268,31 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { } const GetUserByPhone = `-- name: GetUserByPhone :one -SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at FROM users WHERE phone_number = $1 ` type GetUserByPhoneRow struct { - ID int64 - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - EmailVerified bool - PhoneVerified bool - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) (GetUserByPhoneRow, error) { @@ -258,17 +313,82 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) ( return i, err } +const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many +SELECT id, + first_name, + last_name, + email, + phone_number, + role, + email_verified, + phone_verified, + created_at, + updated_at +FROM users +WHERE first_name ILIKE '%' || $1 || '%' + OR last_name ILIKE '%' || $1 || '%' + OR phone_number LIKE '%' || $1 || '%' +` + +type SearchUserByNameOrPhoneRow struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + EmailVerified bool `json:"email_verified"` + PhoneVerified bool `json:"phone_verified"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.Text) ([]SearchUserByNameOrPhoneRow, error) { + rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SearchUserByNameOrPhoneRow + for rows.Next() { + var i SearchUserByNameOrPhoneRow + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Role, + &i.EmailVerified, + &i.PhoneVerified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const UpdatePassword = `-- name: UpdatePassword :exec UPDATE users -SET password = $1, updated_at = $4 -WHERE (email = $2 OR phone_number = $3) +SET password = $1, + updated_at = $4 +WHERE ( + email = $2 + OR phone_number = $3 + ) ` type UpdatePasswordParams struct { - Password []byte - Email pgtype.Text - PhoneNumber pgtype.Text - UpdatedAt pgtype.Timestamptz + Password []byte `json:"password"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error { @@ -283,18 +403,23 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) const UpdateUser = `-- name: UpdateUser :exec UPDATE users -SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = $6 +SET first_name = $1, + last_name = $2, + email = $3, + phone_number = $4, + role = $5, + updated_at = $6 WHERE id = $7 ` type UpdateUserParams struct { - FirstName string - LastName string - Email pgtype.Text - PhoneNumber pgtype.Text - Role string - UpdatedAt pgtype.Timestamptz - ID int64 + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email pgtype.Text `json:"email"` + PhoneNumber pgtype.Text `json:"phone_number"` + Role string `json:"role"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + ID int64 `json:"id"` } func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error { diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 7c57b08..b3637f8 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -12,14 +12,21 @@ import ( ) const CreateCustomerWallet = `-- name: CreateCustomerWallet :one -INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING id, customer_id, company_id, regular_wallet_id, static_wallet_id, created_at, updated_at +INSERT INTO customer_wallets ( + customer_id, + company_id, + regular_wallet_id, + static_wallet_id + ) +VALUES ($1, $2, $3, $4) +RETURNING id, customer_id, company_id, regular_wallet_id, static_wallet_id, created_at, updated_at ` type CreateCustomerWalletParams struct { - CustomerID int64 - CompanyID int64 - RegularWalletID int64 - StaticWalletID int64 + CustomerID int64 `json:"customer_id"` + CompanyID int64 `json:"company_id"` + RegularWalletID int64 `json:"regular_wallet_id"` + StaticWalletID int64 `json:"static_wallet_id"` } func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWalletParams) (CustomerWallet, error) { @@ -43,23 +50,37 @@ func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWa } const CreateWallet = `-- name: CreateWallet :one -INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance +INSERT INTO wallets ( + is_withdraw, + is_bettable, + is_transferable, + user_id + ) +VALUES ($1, $2, $3, $4) +RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance ` type CreateWalletParams struct { - IsWithdraw bool - IsBettable bool - UserID int64 + IsWithdraw bool `json:"is_withdraw"` + IsBettable bool `json:"is_bettable"` + IsTransferable bool `json:"is_transferable"` + UserID int64 `json:"user_id"` } func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wallet, error) { - row := q.db.QueryRow(ctx, CreateWallet, arg.IsWithdraw, arg.IsBettable, arg.UserID) + row := q.db.QueryRow(ctx, CreateWallet, + arg.IsWithdraw, + arg.IsBettable, + arg.IsTransferable, + arg.UserID, + ) var i Wallet err := row.Scan( &i.ID, &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -70,8 +91,68 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal return i, err } +const GetAllBranchWallets = `-- name: GetAllBranchWallets :many +SELECT wallets.id, + wallets.balance, + wallets.is_active, + wallets.updated_at, + wallets.created_at, + branches.name, + branches.location, + branches.branch_manager_id, + branches.company_id, + branches.is_self_owned +FROM branches + JOIN wallets ON branches.wallet_id = wallets.id +` + +type GetAllBranchWalletsRow struct { + ID int64 `json:"id"` + Balance int64 `json:"balance"` + IsActive bool `json:"is_active"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + Name string `json:"name"` + Location string `json:"location"` + BranchManagerID int64 `json:"branch_manager_id"` + CompanyID int64 `json:"company_id"` + IsSelfOwned bool `json:"is_self_owned"` +} + +func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWalletsRow, error) { + rows, err := q.db.Query(ctx, GetAllBranchWallets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllBranchWalletsRow + for rows.Next() { + var i GetAllBranchWalletsRow + if err := rows.Scan( + &i.ID, + &i.Balance, + &i.IsActive, + &i.UpdatedAt, + &i.CreatedAt, + &i.Name, + &i.Location, + &i.BranchManagerID, + &i.CompanyID, + &i.IsSelfOwned, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAllWallets = `-- name: GetAllWallets :many -SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance +FROM wallets ` func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { @@ -88,6 +169,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -106,8 +188,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { } const GetCustomerWallet = `-- name: GetCustomerWallet :one -SELECT - cw.id, +SELECT cw.id, cw.customer_id, cw.company_id, rw.id AS regular_id, @@ -118,27 +199,28 @@ SELECT sw.updated_at as static_updated_at, cw.created_at FROM customer_wallets cw -JOIN wallets rw ON cw.regular_wallet_id = rw.id -JOIN wallets sw ON cw.static_wallet_id = sw.id -WHERE cw.customer_id = $1 AND cw.company_id = $2 + JOIN wallets rw ON cw.regular_wallet_id = rw.id + JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 + AND cw.company_id = $2 ` type GetCustomerWalletParams struct { - CustomerID int64 - CompanyID int64 + CustomerID int64 `json:"customer_id"` + CompanyID int64 `json:"company_id"` } type GetCustomerWalletRow struct { - ID int64 - CustomerID int64 - CompanyID int64 - RegularID int64 - RegularBalance int64 - StaticID int64 - StaticBalance int64 - RegularUpdatedAt pgtype.Timestamp - StaticUpdatedAt pgtype.Timestamp - CreatedAt pgtype.Timestamp + ID int64 `json:"id"` + CustomerID int64 `json:"customer_id"` + CompanyID int64 `json:"company_id"` + RegularID int64 `json:"regular_id"` + RegularBalance int64 `json:"regular_balance"` + StaticID int64 `json:"static_id"` + StaticBalance int64 `json:"static_balance"` + RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"` + StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` } func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletParams) (GetCustomerWalletRow, error) { @@ -160,7 +242,9 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletPa } const GetWalletByID = `-- name: GetWalletByID :one -SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets WHERE id = $1 +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance +FROM wallets +WHERE id = $1 ` func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { @@ -171,6 +255,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -182,7 +267,9 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { } const GetWalletByUserID = `-- name: GetWalletByUserID :many -SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets WHERE user_id = $1 +SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance +FROM wallets +WHERE user_id = $1 ` func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet, error) { @@ -199,6 +286,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet &i.Balance, &i.IsWithdraw, &i.IsBettable, + &i.IsTransferable, &i.UserID, &i.IsActive, &i.CreatedAt, @@ -217,12 +305,15 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet } const UpdateBalance = `-- name: UpdateBalance :exec -UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +UPDATE wallets +SET balance = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2 ` type UpdateBalanceParams struct { - Balance int64 - ID int64 + Balance int64 `json:"balance"` + ID int64 `json:"id"` } func (q *Queries) UpdateBalance(ctx context.Context, arg UpdateBalanceParams) error { @@ -231,12 +322,15 @@ func (q *Queries) UpdateBalance(ctx context.Context, arg UpdateBalanceParams) er } const UpdateWalletActive = `-- name: UpdateWalletActive :exec -UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +UPDATE wallets +SET is_active = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = $2 ` type UpdateWalletActiveParams struct { - IsActive bool - ID int64 + IsActive bool `json:"is_active"` + ID int64 `json:"id"` } func (q *Queries) UpdateWalletActive(ctx context.Context, arg UpdateWalletActiveParams) error { diff --git a/go.mod b/go.mod index 440298d..2c2e549 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 + github.com/robfig/cron/v3 v3.0.1 github.com/swaggo/fiber-swagger v1.3.0 github.com/swaggo/swag v1.16.4 golang.org/x/crypto v0.36.0 diff --git a/go.sum b/go.sum index 6539eff..67b5c0c 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/internal/config/config.go b/internal/config/config.go index e041a8b..e58f153 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,7 @@ var ( ErrInvalidLevel = errors.New("invalid log level") ErrInvalidEnv = errors.New("env not set or invalid") ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid") + ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env") ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid") ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid") ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid") @@ -39,6 +40,7 @@ type Config struct { AFRO_SMS_SENDER_NAME string AFRO_SMS_RECEIVER_PHONE_NUMBER string ADRO_SMS_HOST_URL string + Bet365Token string PopOK domain.PopOKConfig } @@ -157,5 +159,10 @@ func (c *Config) loadEnv() error { BaseURL: popOKBaseURL, CallbackURL: popOKCallbackURL, } + betToken := os.Getenv("BET365_TOKEN") + if betToken == "" { + return ErrMissingBetToken + } + c.Bet365Token = betToken return nil } diff --git a/internal/domain/bank.go b/internal/domain/bank.go new file mode 100644 index 0000000..7d0427a --- /dev/null +++ b/internal/domain/bank.go @@ -0,0 +1 @@ +package domain \ No newline at end of file diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 87bc936..af9f03b 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -1,44 +1,81 @@ package domain -type BetStatus int - -const ( - BET_STATUS_PENDING BetStatus = iota - BET_STATUS_WIN - BET_STATUS_LOSS - BET_STATUS_ERROR +import ( + "time" ) +type BetOutcome struct { + ID int64 `json:"id" example:"1"` + BetID int64 `json:"bet_id" example:"1"` + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` + HomeTeamName string `json:"home_team_name" example:"Manchester"` + AwayTeamName string `json:"away_team_name" example:"Liverpool"` + MarketID int64 `json:"market_id" example:"1"` + MarketName string `json:"market_name" example:"Fulltime Result"` + Odd float32 `json:"odd" example:"1.5"` + OddName string `json:"odd_name" example:"1"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Status OutcomeStatus `json:"status" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` +} + +type CreateBetOutcome struct { + BetID int64 `json:"bet_id" example:"1"` + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` + HomeTeamName string `json:"home_team_name" example:"Manchester"` + AwayTeamName string `json:"away_team_name" example:"Liverpool"` + MarketID int64 `json:"market_id" example:"1"` + MarketName string `json:"market_name" example:"Fulltime Result"` + Odd float32 `json:"odd" example:"1.5"` + OddName string `json:"odd_name" example:"1"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` +} + // If it is a ShopBet then UserID will be the cashier // If it is a DigitalBet then UserID will be the user and the branchID will be 0 or nil type Bet struct { ID int64 - Outcomes []Outcome Amount Currency TotalOdds float32 - Status BetStatus + Status OutcomeStatus FullName string PhoneNumber string BranchID ValidInt64 // Can Be Nullable UserID ValidInt64 // Can Be Nullable IsShopBet bool CashedOut bool + CashoutID string } -type CreateBet struct { - Outcomes []int64 +type GetBet struct { + ID int64 Amount Currency TotalOdds float32 - Status BetStatus + Status OutcomeStatus FullName string PhoneNumber string BranchID ValidInt64 // Can Be Nullable UserID ValidInt64 // Can Be Nullable IsShopBet bool + CashedOut bool + CashoutID string + Outcomes []BetOutcome } -func (b BetStatus) String() string { - return []string{"Pending", "Win", "Loss", "Error"}[b] +type CreateBet struct { + Amount Currency + TotalOdds float32 + Status OutcomeStatus + FullName string + PhoneNumber string + BranchID ValidInt64 // Can Be Nullable + UserID ValidInt64 // Can Be Nullable + IsShopBet bool + CashoutID string } -// func isBetStatusValid() diff --git a/internal/domain/branch.go b/internal/domain/branch.go index dc4a07f..3295dae 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -1,14 +1,61 @@ package domain type Branch struct { - ID int64 - Name string - Location string - WalletID int64 - BranchManagerID int64 - IsSelfOwned bool - IsSupportingSportBook bool - IsSupportingVirtual bool - IsSupportingGameZone bool + ID int64 + Name string + Location string + WalletID int64 + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool } +type BranchDetail struct { + ID int64 + Name string + Location string + WalletID int64 + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool + ManagerName string + ManagerPhoneNumber string +} + +type SupportedOperation struct { + ID int64 + Name string + Description string +} + +type BranchOperation struct { + ID int64 + OperationName string + OperationDescription string +} + +type CreateBranch struct { + Name string + Location string + WalletID int64 + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool +} + +type UpdateBranch struct { + Name string + Location string + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool +} + +type CreateSupportedOperation struct { + Name string + Description string +} +type CreateBranchOperation struct { + BranchID int64 + OperationID int64 +} diff --git a/internal/domain/chapa.go b/internal/domain/chapa.go new file mode 100644 index 0000000..7d0427a --- /dev/null +++ b/internal/domain/chapa.go @@ -0,0 +1 @@ +package domain \ No newline at end of file diff --git a/internal/domain/common.go b/internal/domain/common.go index 985e97e..e3a5e52 100644 --- a/internal/domain/common.go +++ b/internal/domain/common.go @@ -35,5 +35,17 @@ func (m Currency) String() string { x := float32(m) x = x / 100 return fmt.Sprintf("$%.2f", x) - +} + +type OutcomeStatus int + +const ( + OUTCOME_STATUS_PENDING OutcomeStatus = iota + OUTCOME_STATUS_WIN + OUTCOME_STATUS_LOSS + OUTCOME_STATUS_ERROR +) + +func (b OutcomeStatus) String() string { + return []string{"Pending", "Win", "Loss", "Error"}[b] } diff --git a/internal/domain/company.go b/internal/domain/company.go new file mode 100644 index 0000000..989f306 --- /dev/null +++ b/internal/domain/company.go @@ -0,0 +1,21 @@ +package domain + +// Company represents the client that we will contract the services with +// they are the ones that manage the branches and branch managers +// they will have their own wallet that they will use to distribute to the branch wallets +type Company struct { + ID int64 + Name string + AdminID int64 + WalletID int64 +} +type CreateCompany struct { + Name string + AdminID int64 + WalletID int64 +} + +type UpdateCompany struct { + Name string + AdminID int64 +} diff --git a/internal/domain/event.go b/internal/domain/event.go index e5cc881..0a69607 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -1,6 +1,41 @@ package domain -type Event struct {} - -type Outcome struct {} +import "time" +type Event struct { + ID string + SportID string + MatchName string + HomeTeam string + AwayTeam string + HomeTeamID string + AwayTeamID string + HomeKitImage string + AwayKitImage string + LeagueID string + LeagueName string + LeagueCC string + StartTime string + Score string + MatchMinute int + TimerStatus string + AddedTime int + MatchPeriod int + IsLive bool + Status string +} +type UpcomingEvent struct { + ID string // Event ID + SportID string // Sport ID + MatchName string // Match or event name + HomeTeam string // Home team name (if available) + AwayTeam string // Away team name (can be empty/null) + HomeTeamID string // Home team ID + AwayTeamID string // Away team ID (can be empty/null) + HomeKitImage string // Kit or image for home team (optional) + AwayKitImage string // Kit or image for away team (optional) + LeagueID string // League ID + LeagueName string // League name + LeagueCC string // League country code + StartTime time.Time // Converted from "time" field in UNIX format +} \ No newline at end of file diff --git a/internal/domain/odds.go b/internal/domain/odds.go new file mode 100644 index 0000000..990c6a0 --- /dev/null +++ b/internal/domain/odds.go @@ -0,0 +1,47 @@ +package domain + +import ( + "encoding/json" + "time" +) + +type RawMessage interface{} + +type Market struct { + EventID string + FI string + MarketCategory string + MarketType string + MarketName string + MarketID string + UpdatedAt time.Time + Odds []json.RawMessage + Name string + Handicap string + OddsVal float64 +} + +type Odd struct { + EventID string `json:"event_id"` + Fi string `json:"fi"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID string `json:"market_id"` + Name string `json:"name"` + Handicap string `json:"handicap"` + OddsValue float64 `json:"odds_value"` + Section string `json:"section"` + Category string `json:"category"` + RawOdds []RawMessage `json:"raw_odds"` + FetchedAt time.Time `json:"fetched_at"` + Source string `json:"source"` + IsActive bool `json:"is_active"` +} +type RawOddsByMarketID struct { + ID int64 `json:"id"` + MarketName string `json:"market_name"` + Handicap string `json:"handicap"` + RawOdds []RawMessage `json:"raw_odds"` + FetchedAt time.Time `json:"fetched_at"` +} diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index b1c000f..15dd180 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -1,15 +1,54 @@ package domain +import "time" + +type TicketOutcome struct { + ID int64 `json:"id" example:"1"` + TicketID int64 `json:"ticket_id" example:"1"` + EventID int64 `json:"event_id" example:"1"` + HomeTeamName string `json:"home_team_name" example:"Manchester"` + AwayTeamName string `json:"away_team_name" example:"Liverpool"` + MarketID int64 `json:"market_id" example:"1"` + MarketName string `json:"market_name" example:"Fulltime Result"` + OddID int64 `json:"odd_id" example:"1"` + Odd float32 `json:"odd" example:"1.5"` + OddName string `json:"odd_name" example:"1"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Status OutcomeStatus `json:"status" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` +} + +type CreateTicketOutcome struct { + TicketID int64 `json:"ticket_id" example:"1"` + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` + HomeTeamName string `json:"home_team_name" example:"Manchester"` + AwayTeamName string `json:"away_team_name" example:"Liverpool"` + MarketID int64 `json:"market_id" example:"1"` + MarketName string `json:"market_name" example:"Fulltime Result"` + Odd float32 `json:"odd" example:"1.5"` + OddName string `json:"odd_name" example:"1"` + OddHeader string `json:"odd_header" example:"1"` + OddHandicap string `json:"odd_handicap" example:"1"` + Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` +} + // ID will serve as the fast code since this doesn't need to be secure type Ticket struct { ID int64 - Outcomes []Outcome Amount Currency TotalOdds float32 } +type GetTicket struct { + ID int64 + Amount Currency + TotalOdds float32 + Outcomes []TicketOutcome +} + type CreateTicket struct { - Outcomes []int64 Amount Currency TotalOdds float32 } diff --git a/internal/domain/transaction.go b/internal/domain/transaction.go index b4cdaf2..f47c34a 100644 --- a/internal/domain/transaction.go +++ b/internal/domain/transaction.go @@ -1,5 +1,12 @@ package domain +type TransactionType int + +const ( + TRANSACTION_CASHOUT TransactionType = iota + TRANSACTION_DEPOSIT +) + type PaymentOption int64 const ( @@ -9,12 +16,15 @@ const ( BANK ) +// Transaction only represents when the user cashes out a bet in the shop +// It probably would be better to call it a CashOut or ShopWithdrawal type Transaction struct { ID int64 Amount Currency BranchID int64 CashierID int64 BetID int64 + Type TransactionType PaymentOption PaymentOption FullName string PhoneNumber string @@ -32,6 +42,7 @@ type CreateTransaction struct { BranchID int64 CashierID int64 BetID int64 + Type TransactionType PaymentOption PaymentOption FullName string PhoneNumber string diff --git a/internal/domain/transfer.go b/internal/domain/transfer.go index 6c6210a..845482b 100644 --- a/internal/domain/transfer.go +++ b/internal/domain/transfer.go @@ -1,23 +1,48 @@ package domain +import "time" + type TransferType string const ( DEPOSIT TransferType = "deposit" WITHDRAW TransferType = "withdraw" + WALLET TransferType = "wallet" ) +type PaymentMethod string + +const ( + TRANSFER_CASH PaymentMethod = "cash" + TRANSFER_BANK PaymentMethod = "bank" + TRANSFER_CHAPA PaymentMethod = "chapa" + TRANSFER_ARIFPAY PaymentMethod = "arifpay" + TRANSFER_SANTIMPAY PaymentMethod = "santimpay" + TRANSFER_ADDISPAY PaymentMethod = "addispay" + TRANSFER_OTHER PaymentMethod = "other" +) + +// There is always a receiving wallet id +// There is a sender wallet id only if wallet transfer type type Transfer struct { - ID int64 - Amount Currency - Verified bool - WalletID int64 - Type TransferType + ID int64 + Amount Currency + Verified bool + Type TransferType + PaymentMethod PaymentMethod + ReceiverWalletID int64 + SenderWalletID ValidInt64 + CashierID ValidInt64 + CreatedAt time.Time + UpdatedAt time.Time } type CreateTransfer struct { - Amount Currency - Verified bool - WalletID int64 - Type TransferType + Amount Currency + Verified bool + ReceiverWalletID int64 + SenderWalletID ValidInt64 + CashierID ValidInt64 + Type TransferType + PaymentMethod PaymentMethod } diff --git a/internal/domain/user.go b/internal/domain/user.go index 89c0d95..c215c5e 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -26,6 +26,8 @@ type User struct { // SuspendedAt time.Time Suspended bool + // + BranchID int64 } type RegisterUserReq struct { FirstName string @@ -33,11 +35,19 @@ type RegisterUserReq struct { Email string PhoneNumber string Password string - //Role string + Role string Otp string ReferralCode string `json:"referral_code"` OtpMedium OtpMedium } +type CreateUserReq struct { + FirstName string + LastName string + Email string + PhoneNumber string + Password string + Role string +} type ResetPasswordReq struct { Email string PhoneNumber string @@ -46,6 +56,7 @@ type ResetPasswordReq struct { OtpMedium OtpMedium } type UpdateUserReq struct { + UserId int64 FirstName ValidString LastName ValidString Suspended ValidBool diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index ff92ef6..33e9466 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -3,22 +3,23 @@ package domain import "time" type Wallet struct { - ID int64 - Balance Currency - IsWithdraw bool - IsBettable bool - IsActive bool - UserID int64 - UpdatedAt time.Time - CreatedAt time.Time + ID int64 + Balance Currency + IsWithdraw bool + IsBettable bool + IsTransferable bool + IsActive bool + UserID int64 + UpdatedAt time.Time + CreatedAt time.Time } type CustomerWallet struct { - ID int64 - RegularID int64 - StaticID int64 - CustomerID int64 - CompanyID int64 + ID int64 + RegularID int64 + StaticID int64 + CustomerID int64 + CompanyID int64 } type GetCustomerWallet struct { ID int64 @@ -33,10 +34,24 @@ type GetCustomerWallet struct { CreatedAt time.Time } +type BranchWallet struct { + ID int64 + Balance Currency + IsActive bool + Name string + Location string + BranchManagerID int64 + CompanyID int64 + IsSelfOwned bool + UpdatedAt time.Time + CreatedAt time.Time +} + type CreateWallet struct { - IsWithdraw bool - IsBettable bool - UserID int64 + IsWithdraw bool + IsBettable bool + IsTransferable bool + UserID int64 } type CreateCustomerWallet struct { diff --git a/internal/repository/bet.go b/internal/repository/bet.go index c4d0362..c19db94 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -2,6 +2,7 @@ package repository import ( "context" + // "fmt" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -13,7 +14,7 @@ func convertDBBet(bet dbgen.Bet) domain.Bet { ID: bet.ID, Amount: domain.Currency(bet.Amount), TotalOdds: bet.TotalOdds, - Status: domain.BetStatus(bet.Status), + Status: domain.OutcomeStatus(bet.Status), FullName: bet.FullName, PhoneNumber: bet.PhoneNumber, BranchID: domain.ValidInt64{ @@ -25,6 +26,71 @@ func convertDBBet(bet dbgen.Bet) domain.Bet { Valid: bet.UserID.Valid, }, IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + CashoutID: bet.CashoutID, + } +} + +func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { + var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes)) + + for _, outcome := range bet.Outcomes { + outcomes = append(outcomes, domain.BetOutcome{ + ID: outcome.ID, + BetID: outcome.BetID, + EventID: outcome.EventID, + OddID: outcome.OddID, + HomeTeamName: outcome.HomeTeamName, + AwayTeamName: outcome.AwayTeamName, + MarketID: outcome.MarketID, + MarketName: outcome.MarketName, + Odd: outcome.Odd, + OddName: outcome.OddName, + OddHeader: outcome.OddHeader, + OddHandicap: outcome.OddHandicap, + Status: domain.OutcomeStatus(outcome.Status), + Expires: outcome.Expires.Time, + }) + } + return domain.GetBet{ + ID: bet.ID, + Amount: domain.Currency(bet.Amount), + TotalOdds: bet.TotalOdds, + Status: domain.OutcomeStatus(bet.Status), + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + BranchID: domain.ValidInt64{ + Value: bet.BranchID.Int64, + Valid: bet.BranchID.Valid, + }, + UserID: domain.ValidInt64{ + Value: bet.UserID.Int64, + Valid: bet.UserID.Valid, + }, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + CashoutID: bet.CashoutID, + Outcomes: outcomes, + } +} + +func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams { + return dbgen.CreateBetOutcomeParams{ + BetID: betOutcome.BetID, + EventID: betOutcome.EventID, + OddID: betOutcome.OddID, + HomeTeamName: betOutcome.HomeTeamName, + AwayTeamName: betOutcome.AwayTeamName, + MarketID: betOutcome.MarketID, + MarketName: betOutcome.MarketName, + Odd: betOutcome.Odd, + OddName: betOutcome.OddName, + OddHeader: betOutcome.OddHeader, + OddHandicap: betOutcome.OddHandicap, + Expires: pgtype.Timestamp{ + Time: betOutcome.Expires, + Valid: true, + }, } } @@ -44,11 +110,11 @@ func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams { Valid: bet.UserID.Valid, }, IsShopBet: bet.IsShopBet, + CashoutID: bet.CashoutID, } } func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { - newBet, err := s.queries.CreateBet(ctx, convertCreateBet(bet)) if err != nil { return domain.Bet{}, err @@ -57,25 +123,68 @@ func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet } -func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.Bet, error) { - bet, err := s.queries.GetBetByID(ctx, id) +func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) { + var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes)) + + for _, outcome := range outcomes { + dbParams = append(dbParams, convertDBCreateBetOutcome(outcome)) + } + rows, err := s.queries.CreateBetOutcome(ctx, dbParams) + if err != nil { - return domain.Bet{}, err + return rows, err } - return convertDBBet(bet), nil + return rows, nil } -func (s *Store) GetAllBets(ctx context.Context) ([]domain.Bet, error) { +func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { + bet, err := s.queries.GetBetByID(ctx, id) + + if err != nil { + return domain.GetBet{}, err + } + + return convertDBBetOutcomes(bet), nil +} + +func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) { + bet, err := s.queries.GetBetByCashoutID(ctx, id) + + if err != nil { + return domain.GetBet{}, err + } + + return convertDBBetOutcomes(bet), nil +} + +func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) { bets, err := s.queries.GetAllBets(ctx) + if err != nil { + return nil, err + } + + var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) + for _, bet := range bets { + result = append(result, convertDBBetOutcomes(bet)) + } + + return result, nil +} + +func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) { + bets, err := s.queries.GetBetByBranchID(ctx, pgtype.Int8{ + Int64: BranchID, + Valid: true, + }) if err != nil { return nil, err } - var result []domain.Bet = make([]domain.Bet, len(bets)) + var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) for _, bet := range bets { - result = append(result, convertDBBet(bet)) + result = append(result, convertDBBetOutcomes(bet)) } return result, nil @@ -83,10 +192,24 @@ func (s *Store) GetAllBets(ctx context.Context) ([]domain.Bet, error) { func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{ - ID: id, - CashedOut: pgtype.Bool{ - Bool: cashedOut, - }, + ID: id, + CashedOut: cashedOut, + }) + return err +} + +func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + err := s.queries.UpdateStatus(ctx, dbgen.UpdateStatusParams{ + ID: id, + Status: int32(status), + }) + return err +} + +func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{ + Status: int32(status), + ID: id, }) return err } diff --git a/internal/repository/branch.go b/internal/repository/branch.go new file mode 100644 index 0000000..6287380 --- /dev/null +++ b/internal/repository/branch.go @@ -0,0 +1,224 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) + +func convertCreateBranch(branch domain.CreateBranch) dbgen.CreateBranchParams { + return dbgen.CreateBranchParams{ + Name: branch.Name, + Location: branch.Location, + WalletID: branch.WalletID, + BranchManagerID: branch.BranchManagerID, + CompanyID: branch.CompanyID, + IsSelfOwned: branch.IsSelfOwned, + } +} + +func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail { + return domain.BranchDetail{ + ID: dbBranch.ID, + Name: dbBranch.Name, + Location: dbBranch.Location, + WalletID: dbBranch.WalletID, + BranchManagerID: dbBranch.BranchManagerID, + CompanyID: dbBranch.CompanyID, + IsSelfOwned: dbBranch.IsSelfOwned, + ManagerName: dbBranch.ManagerName.(string), + ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String, + } +} + +func convertDBBranch(dbBranch dbgen.Branch) domain.Branch { + return domain.Branch{ + ID: dbBranch.ID, + Name: dbBranch.Name, + Location: dbBranch.Location, + WalletID: dbBranch.WalletID, + BranchManagerID: dbBranch.BranchManagerID, + CompanyID: dbBranch.CompanyID, + IsSelfOwned: dbBranch.IsSelfOwned, + } +} + +func (s *Store) CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) { + + dbBranch, err := s.queries.CreateBranch(ctx, convertCreateBranch(branch)) + + if err != nil { + return domain.Branch{}, err + } + return convertDBBranch(dbBranch), nil +} + +func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { + dbBranch, err := s.queries.GetBranchByID(ctx, id) + if err != nil { + return domain.BranchDetail{}, err + } + return convertDBBranchDetail(dbBranch), nil +} + +func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.GetBranchByManagerID(ctx, branchManagerID) + if err != nil { + return nil, err + } + var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) + for _, dbBranch := range dbBranches { + branches = append(branches, convertDBBranchDetail(dbBranch)) + } + return branches, nil +} +func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.GetBranchByCompanyID(ctx, companyID) + if err != nil { + return nil, err + } + var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) + for _, dbBranch := range dbBranches { + branches = append(branches, convertDBBranchDetail(dbBranch)) + } + return branches, nil +} + +func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.GetAllBranches(ctx) + if err != nil { + return nil, err + } + var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) + for _, dbBranch := range dbBranches { + branches = append(branches, convertDBBranchDetail(dbBranch)) + } + return branches, nil +} + +func (s *Store) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.SearchBranchByName(ctx, pgtype.Text{String: name, Valid: true}) + if err != nil { + return nil, err + } + + var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) + for _, dbBranch := range dbBranches { + branches = append(branches, convertDBBranchDetail(dbBranch)) + } + return branches, nil +} + +func (s *Store) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) { + dbBranch, err := s.queries.UpdateBranch(ctx, dbgen.UpdateBranchParams{ + ID: id, + Name: branch.Name, + Location: branch.Location, + BranchManagerID: branch.BranchManagerID, + IsSelfOwned: branch.IsSelfOwned, + }) + if err != nil { + return domain.Branch{}, err + } + return convertDBBranch(dbBranch), nil +} + +func (s *Store) DeleteBranch(ctx context.Context, id int64) error { + return s.queries.DeleteBranch(ctx, id) +} + +// Branch Operations + +func (s *Store) CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error { + _, err := s.queries.CreateBranchOperation(ctx, dbgen.CreateBranchOperationParams{ + BranchID: branchOperation.BranchID, + OperationID: branchOperation.OperationID, + }) + return err +} + +func (s *Store) CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) { + dbSupportedOperation, err := s.queries.CreateSupportedOperation(ctx, dbgen.CreateSupportedOperationParams{ + Name: supportedOperation.Name, + Description: supportedOperation.Description, + }) + if err != nil { + return domain.SupportedOperation{}, err + } + return domain.SupportedOperation{ + ID: dbSupportedOperation.ID, + Name: dbSupportedOperation.Name, + Description: dbSupportedOperation.Description, + }, nil +} + +func (s *Store) CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error { + _, err := s.queries.CreateBranchCashier(ctx, dbgen.CreateBranchCashierParams{ + UserID: userID, + BranchID: branchID, + }) + + if err != nil { + return err + } + return nil +} + +func (s *Store) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { + dbOperations, err := s.queries.GetAllSupportedOperations(ctx) + if err != nil { + return nil, err + } + + var operations []domain.SupportedOperation = make([]domain.SupportedOperation, 0, len(dbOperations)) + for _, dbOperation := range dbOperations { + operations = append(operations, domain.SupportedOperation{ + ID: dbOperation.ID, + Name: dbOperation.Name, + Description: dbOperation.Description, + }) + } + return operations, nil + +} + +func (s *Store) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) { + dbBranchOperations, err := s.queries.GetBranchOperations(ctx, branchID) + if err != nil { + return nil, err + } + var branchOperations []domain.BranchOperation = make([]domain.BranchOperation, 0, len(dbBranchOperations)) + for _, dbBranchOperation := range dbBranchOperations { + branchOperations = append(branchOperations, domain.BranchOperation{ + ID: dbBranchOperation.ID, + OperationName: dbBranchOperation.Name, + OperationDescription: dbBranchOperation.Description, + }) + } + return branchOperations, nil +} + +func (s *Store) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) { + branch, err := s.queries.GetBranchByCashier(ctx, userID) + if err != nil { + return domain.Branch{}, err + } + + return convertDBBranch(branch), err +} + + +func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { + err := s.queries.DeleteBranchOperation(ctx, dbgen.DeleteBranchOperationParams{ + BranchID: branchID, + OperationID: operationID, + }) + return err +} + +func (s *Store) DeleteBranchCashier(ctx context.Context, userID int64) error { + return s.queries.DeleteBranchCashier(ctx, userID) + +} diff --git a/internal/repository/company.go b/internal/repository/company.go new file mode 100644 index 0000000..0e52cb6 --- /dev/null +++ b/internal/repository/company.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams { + return dbgen.CreateCompanyParams{ + Name: company.Name, + AdminID: company.AdminID, + WalletID: company.WalletID, + } +} + +func convertDBCompany(dbCompany dbgen.Company) domain.Company { + return domain.Company{ + ID: dbCompany.ID, + Name: dbCompany.Name, + AdminID: dbCompany.AdminID, + WalletID: dbCompany.WalletID, + } +} + +func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { + dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company)) + if err != nil { + return domain.Company{}, err + } + return convertDBCompany(dbCompany), nil +} + +func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { + dbCompanies, err := s.queries.GetAllCompanies(ctx) + if err != nil { + return nil, err + } + + var companies []domain.Company = make([]domain.Company, 0, len(dbCompanies)) + for _, dbCompany := range dbCompanies { + companies = append(companies, convertDBCompany(dbCompany)) + } + + return companies, nil +} + +func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { + dbCompany, err := s.queries.GetCompanyByID(ctx, id) + + if err != nil { + return domain.Company{}, err + } + return convertDBCompany(dbCompany), nil +} + +func (s *Store) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { + dbCompany, err := s.queries.UpdateCompany(ctx, dbgen.UpdateCompanyParams{ + ID: id, + Name: company.Name, + AdminID: company.AdminID, + }) + + if err != nil { + return domain.Company{}, err + } + + return convertDBCompany(dbCompany), nil +} + +func (s *Store) DeleteCompany(ctx context.Context, id int64) error { + return s.queries.DeleteCompany(ctx, id) +} diff --git a/internal/repository/event.go b/internal/repository/event.go new file mode 100644 index 0000000..b64973d --- /dev/null +++ b/internal/repository/event.go @@ -0,0 +1,165 @@ +package repository + +import ( + "context" + "math" + "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + + // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" + "github.com/jackc/pgx/v5/pgtype" +) + +func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error { + parsedTime, err := time.Parse(time.RFC3339, e.StartTime) + if err != nil { + return err + } + + return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{ + ID: e.ID, + SportID: pgtype.Text{String: e.SportID, Valid: true}, + MatchName: pgtype.Text{String: e.MatchName, Valid: true}, + HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true}, + AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true}, + HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true}, + AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true}, + HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true}, + AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true}, + LeagueID: pgtype.Text{String: e.LeagueID, Valid: true}, + LeagueName: pgtype.Text{String: e.LeagueName, Valid: true}, + LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true}, + StartTime: pgtype.Timestamp{Time: parsedTime, Valid: true}, + Score: pgtype.Text{String: e.Score, Valid: true}, + MatchMinute: pgtype.Int4{Int32: int32(e.MatchMinute), Valid: true}, + TimerStatus: pgtype.Text{String: e.TimerStatus, Valid: true}, + AddedTime: pgtype.Int4{Int32: int32(e.AddedTime), Valid: true}, + MatchPeriod: pgtype.Int4{Int32: int32(e.MatchPeriod), Valid: true}, + IsLive: pgtype.Bool{Bool: e.IsLive, Valid: true}, + Status: pgtype.Text{String: e.Status, Valid: true}, + }) +} +func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error { + return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{ + ID: e.ID, + SportID: pgtype.Text{String: e.SportID, Valid: true}, + MatchName: pgtype.Text{String: e.MatchName, Valid: true}, + HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true}, + AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true}, + HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true}, + AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true}, + HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true}, + AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true}, + LeagueID: pgtype.Text{String: e.LeagueID, Valid: true}, + LeagueName: pgtype.Text{String: e.LeagueName, Valid: true}, + LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true}, + StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true}, + }) +} + +func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) { + return s.queries.ListLiveEvents(ctx) +} +func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { + events, err := s.queries.GetAllUpcomingEvents(ctx) + if err != nil { + return nil, err + } + + upcomingEvents := make([]domain.UpcomingEvent, len(events)) + for i, e := range events { + upcomingEvents[i] = domain.UpcomingEvent{ + ID: e.ID, + SportID: e.SportID.String, + MatchName: e.MatchName.String, + HomeTeam: e.HomeTeam.String, + AwayTeam: e.AwayTeam.String, + HomeTeamID: e.HomeTeamID.String, + AwayTeamID: e.AwayTeamID.String, + HomeKitImage: e.HomeKitImage.String, + AwayKitImage: e.AwayKitImage.String, + LeagueID: e.LeagueID.String, + LeagueName: e.LeagueName.String, + LeagueCC: e.LeagueCc.String, + StartTime: e.StartTime.Time.UTC(), + } + } + return upcomingEvents, nil +} +func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) { + events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{ + LeagueID: pgtype.Text{ + String: leagueID.Value, + Valid: leagueID.Valid, + }, + SportID: pgtype.Text{ + String: sportID.Value, + Valid: sportID.Valid, + }, + Limit: limit, + Offset: offset * limit, + }) + + if err != nil { + return nil, 0, err + } + + upcomingEvents := make([]domain.UpcomingEvent, len(events)) + for i, e := range events { + upcomingEvents[i] = domain.UpcomingEvent{ + ID: e.ID, + SportID: e.SportID.String, + MatchName: e.MatchName.String, + HomeTeam: e.HomeTeam.String, + AwayTeam: e.AwayTeam.String, + HomeTeamID: e.HomeTeamID.String, + AwayTeamID: e.AwayTeamID.String, + HomeKitImage: e.HomeKitImage.String, + AwayKitImage: e.AwayKitImage.String, + LeagueID: e.LeagueID.String, + LeagueName: e.LeagueName.String, + LeagueCC: e.LeagueCc.String, + StartTime: e.StartTime.Time.UTC(), + } + } + totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ + LeagueID: pgtype.Text{ + String: leagueID.Value, + Valid: leagueID.Valid, + }, + SportID: pgtype.Text{ + String: sportID.Value, + Valid: sportID.Valid, + }, + }) + if err != nil { + return nil, 0, err + } + + numberOfPages := math.Ceil(float64(totalCount) / float64(limit)) + return upcomingEvents, int64(numberOfPages), nil +} +func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { + event, err := s.queries.GetUpcomingByID(ctx, ID) + if err != nil { + return domain.UpcomingEvent{}, err + } + + return domain.UpcomingEvent{ + ID: event.ID, + SportID: event.SportID.String, + MatchName: event.MatchName.String, + HomeTeam: event.HomeTeam.String, + AwayTeam: event.AwayTeam.String, + HomeTeamID: event.HomeTeamID.String, + AwayTeamID: event.AwayTeamID.String, + HomeKitImage: event.HomeKitImage.String, + AwayKitImage: event.AwayKitImage.String, + LeagueID: event.LeagueID.String, + LeagueName: event.LeagueName.String, + LeagueCC: event.LeagueCc.String, + StartTime: event.StartTime.Time.UTC(), + }, nil +} diff --git a/internal/repository/odds.go b/internal/repository/odds.go new file mode 100644 index 0000000..31810f5 --- /dev/null +++ b/internal/repository/odds.go @@ -0,0 +1,249 @@ +package repository + +import ( + "context" + "encoding/json" + "os" + "strconv" + "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) + +func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { + if len(m.Odds) == 0 { + return nil + } + + for _, raw := range m.Odds { + var item map[string]interface{} + if err := json.Unmarshal(raw, &item); err != nil { + continue + } + + name := getString(item["name"]) + handicap := getString(item["handicap"]) + oddsVal := getFloat(item["odds"]) + + rawOddsBytes, _ := json.Marshal(m.Odds) + + params := dbgen.InsertNonLiveOddParams{ + EventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""}, + Fi: pgtype.Text{String: m.FI, Valid: m.FI != ""}, + MarketType: m.MarketType, + MarketName: pgtype.Text{String: m.MarketName, Valid: m.MarketName != ""}, + MarketCategory: pgtype.Text{String: m.MarketCategory, Valid: m.MarketCategory != ""}, + MarketID: pgtype.Text{String: m.MarketID, Valid: m.MarketID != ""}, + Name: pgtype.Text{String: name, Valid: name != ""}, + Handicap: pgtype.Text{String: handicap, Valid: handicap != ""}, + OddsValue: pgtype.Float8{Float64: oddsVal, Valid: oddsVal != 0}, + Section: m.MarketCategory, + Category: pgtype.Text{Valid: false}, + RawOdds: rawOddsBytes, + IsActive: pgtype.Bool{Bool: true, Valid: true}, + Source: pgtype.Text{String: "b365api", Valid: true}, + FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + } + + err := s.queries.InsertNonLiveOdd(ctx, params) + if err != nil { + _ = writeFailedMarketLog(m, err) + continue + } + } + return nil +} + +func writeFailedMarketLog(m domain.Market, err error) error { + logDir := "logs" + logFile := logDir + "/failed_markets.log" + + if mkErr := os.MkdirAll(logDir, 0755); mkErr != nil { + return mkErr + } + + f, fileErr := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if fileErr != nil { + return fileErr + } + defer f.Close() + + entry := struct { + Time string `json:"time"` + Error string `json:"error"` + Record domain.Market `json:"record"` + }{ + Time: time.Now().Format(time.RFC3339), + Error: err.Error(), + Record: m, + } + + jsonData, _ := json.MarshalIndent(entry, "", " ") + _, writeErr := f.WriteString(string(jsonData) + "\n\n") + return writeErr +} + +func getString(v interface{}) string { + if s, ok := v.(string); ok { + return s + } + return "" +} + +func getFloat(v interface{}) float64 { + if s, ok := v.(string); ok { + f, err := strconv.ParseFloat(s, 64) + if err == nil { + return f + } + } + return 0 +} + +func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { + odds, err := s.queries.GetPrematchOdds(ctx) + if err != nil { + return nil, err + } + + domainOdds := make([]domain.Odd, len(odds)) + for i, odd := range odds { + domainOdds[i] = domain.Odd{ + EventID: odd.EventID.String, + Fi: odd.Fi.String, + MarketType: odd.MarketType, + MarketName: odd.MarketName.String, + MarketCategory: odd.MarketCategory.String, + MarketID: odd.MarketID.String, + Name: odd.Name.String, + Handicap: odd.Handicap.String, + OddsValue: odd.OddsValue.Float64, + Section: odd.Section, + Category: odd.Category.String, + RawOdds: func() []domain.RawMessage { + var rawOdds []domain.RawMessage + if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil { + rawOdds = nil + } + return rawOdds + }(), + FetchedAt: odd.FetchedAt.Time, + Source: odd.Source.String, + IsActive: odd.IsActive.Bool, + } + } + + return domainOdds, nil +} + +func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { + rows, err := s.queries.GetALLPrematchOdds(ctx) + if err != nil { + return nil, err + } + + domainOdds := make([]domain.Odd, len(rows)) + for i, row := range rows { + domainOdds[i] = domain.Odd{ + // ID: int64(row.ID), + EventID: row.EventID.String, + Fi: row.Fi.String, + MarketType: row.MarketType, + MarketName: row.MarketName.String, + MarketCategory: row.MarketCategory.String, + MarketID: row.MarketID.String, + Name: row.Name.String, + Handicap: row.Handicap.String, + OddsValue: row.OddsValue.Float64, + Section: row.Section, + Category: row.Category.String, + RawOdds: func() []domain.RawMessage { + var rawOdds []domain.RawMessage + if err := json.Unmarshal(row.RawOdds, &rawOdds); err != nil { + rawOdds = nil + } + return rawOdds + }(), + FetchedAt: row.FetchedAt.Time, + Source: row.Source.String, + IsActive: row.IsActive.Bool, + } + } + + return domainOdds, nil +} + +func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upcomingID string) (domain.RawOddsByMarketID, error) { + params := dbgen.GetRawOddsByMarketIDParams{ + MarketID: pgtype.Text{String: rawOddsID, Valid: true}, + Fi: pgtype.Text{String: upcomingID, Valid: true}, + } + + odds, err := s.queries.GetRawOddsByMarketID(ctx, params) + if err != nil { + return domain.RawOddsByMarketID{}, err + } + + var rawOdds []json.RawMessage + if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil { + return domain.RawOddsByMarketID{}, err + } + + return domain.RawOddsByMarketID{ + ID: int64(odds.ID), + MarketName: odds.MarketName.String, + Handicap: odds.Handicap.String, + RawOdds: func() []domain.RawMessage { + converted := make([]domain.RawMessage, len(rawOdds)) + for i, r := range rawOdds { + converted[i] = domain.RawMessage(r) + } + return converted + }(), + FetchedAt: odds.FetchedAt.Time, + }, nil +} + +func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) { + params := dbgen.GetPrematchOddsByUpcomingIDParams{ + ID: upcomingID, + Limit: limit, + Offset: offset, + } + + odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, params) + if err != nil { + return nil, err + } + + // Map the results to domain.Odd + domainOdds := make([]domain.Odd, len(odds)) + for i, odd := range odds { + var rawOdds []domain.RawMessage + if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil { + rawOdds = nil + } + + domainOdds[i] = domain.Odd{ + EventID: odd.EventID.String, + Fi: odd.Fi.String, + MarketType: odd.MarketType, + MarketName: odd.MarketName.String, + MarketCategory: odd.MarketCategory.String, + MarketID: odd.MarketID.String, + Name: odd.Name.String, + Handicap: odd.Handicap.String, + OddsValue: odd.OddsValue.Float64, + Section: odd.Section, + Category: odd.Category.String, + RawOdds: rawOdds, + FetchedAt: odd.FetchedAt.Time, + Source: odd.Source.String, + IsActive: odd.IsActive.Bool, + } + } + + return domainOdds, nil +} diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index d6918d3..5083f65 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -11,16 +11,64 @@ import ( func convertDBTicket(ticket dbgen.Ticket) domain.Ticket { return domain.Ticket{ ID: ticket.ID, - Amount: domain.Currency(ticket.Amount.Int64), + Amount: domain.Currency(ticket.Amount), TotalOdds: ticket.TotalOdds, } } +func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket { + + var outcomes []domain.TicketOutcome = make([]domain.TicketOutcome, 0, len(ticket.Outcomes)) + + for _, outcome := range ticket.Outcomes { + outcomes = append(outcomes, domain.TicketOutcome{ + ID: outcome.ID, + TicketID: outcome.TicketID, + EventID: outcome.EventID, + OddID: outcome.OddID, + HomeTeamName: outcome.HomeTeamName, + AwayTeamName: outcome.AwayTeamName, + MarketID: outcome.MarketID, + MarketName: outcome.MarketName, + Odd: outcome.Odd, + OddName: outcome.OddName, + OddHeader: outcome.OddHeader, + OddHandicap: outcome.OddHandicap, + Status: domain.OutcomeStatus(outcome.Status), + Expires: outcome.Expires.Time, + }) + } + return domain.GetTicket{ + ID: ticket.ID, + Amount: domain.Currency(ticket.Amount), + TotalOdds: ticket.TotalOdds, + Outcomes: outcomes, + } +} + +func convertDBCreateTicketOutcome(ticketOutcome domain.CreateTicketOutcome) dbgen.CreateTicketOutcomeParams { + return dbgen.CreateTicketOutcomeParams{ + TicketID: ticketOutcome.TicketID, + EventID: ticketOutcome.EventID, + OddID: ticketOutcome.OddID, + HomeTeamName: ticketOutcome.HomeTeamName, + AwayTeamName: ticketOutcome.AwayTeamName, + MarketID: ticketOutcome.MarketID, + MarketName: ticketOutcome.MarketName, + Odd: ticketOutcome.Odd, + OddName: ticketOutcome.OddName, + OddHeader: ticketOutcome.OddHeader, + OddHandicap: ticketOutcome.OddHandicap, + Expires: pgtype.Timestamp{ + Time: ticketOutcome.Expires, + Valid: true, + }, + } +} + func convertCreateTicket(ticket domain.CreateTicket) dbgen.CreateTicketParams { return dbgen.CreateTicketParams{ - Amount: pgtype.Int8{ - Int64: int64(ticket.Amount), - }, + Amount: int64(ticket.Amount), TotalOdds: ticket.TotalOdds, } } @@ -35,30 +83,54 @@ func (s *Store) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (d } -func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) { - ticket, err := s.queries.GetTicketByID(ctx, id) - if err != nil { - return domain.Ticket{}, err +func (s *Store) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) { + + var dbParams []dbgen.CreateTicketOutcomeParams = make([]dbgen.CreateTicketOutcomeParams, 0, len(outcomes)) + for _, outcome := range outcomes { + dbParams = append(dbParams, convertDBCreateTicketOutcome(outcome)) } - return convertDBTicket(ticket), nil + rows, err := s.queries.CreateTicketOutcome(ctx, dbParams) + + if err != nil { + return rows, err + } + + return rows, nil } -func (s *Store) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { - tickets, err := s.queries.GetAllTickets(ctx) +func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) { + ticket, err := s.queries.GetTicketByID(ctx, id) + if err != nil { + return domain.GetTicket{}, err + } + + return convertDBTicketOutcomes(ticket), nil +} + +func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { + tickets, err := s.queries.GetAllTickets(ctx) if err != nil { return nil, err } - var result []domain.Ticket = make([]domain.Ticket, len(tickets)) + var result []domain.GetTicket = make([]domain.GetTicket, 0, len(tickets)) for _, ticket := range tickets { - result = append(result, convertDBTicket(ticket)) + result = append(result, convertDBTicketOutcomes(ticket)) } return result, nil } +func (s *Store) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + err := s.queries.UpdateTicketOutcomeStatus(ctx, dbgen.UpdateTicketOutcomeStatusParams{ + Status: int32(status), + ID: id, + }) + return err +} + func (s *Store) DeleteOldTickets(ctx context.Context) error { return s.queries.DeleteOldTickets(ctx) } diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go index 87f04f7..24cf9e0 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/transaction.go @@ -13,6 +13,7 @@ func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { BranchID: transaction.BranchID, CashierID: transaction.CashierID, BetID: transaction.BetID, + Type: domain.TransactionType(transaction.Type), PaymentOption: domain.PaymentOption(transaction.PaymentOption), FullName: transaction.FullName, PhoneNumber: transaction.PhoneNumber, @@ -30,6 +31,7 @@ func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.Create BranchID: transaction.BranchID, CashierID: transaction.CashierID, BetID: transaction.BetID, + Type: int64(transaction.Type), PaymentOption: int64(transaction.PaymentOption), FullName: transaction.FullName, PhoneNumber: transaction.PhoneNumber, @@ -66,7 +68,20 @@ func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, e return nil, err } - var result []domain.Transaction = make([]domain.Transaction, len(transaction)) + var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) + for _, ticket := range transaction { + result = append(result, convertDBTransaction(ticket)) + } + return result, nil +} +func (s *Store) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { + transaction, err := s.queries.GetTransactionByBranch(ctx, id) + + if err != nil { + return nil, err + } + + var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) for _, ticket := range transaction { result = append(result, convertDBTransaction(ticket)) } diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go index d39178b..7ee876e 100644 --- a/internal/repository/transfer.go +++ b/internal/repository/transfer.go @@ -1,77 +1,96 @@ package repository -// import ( -// "context" +import ( + "context" -// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" -// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -// ) + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) -// func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { -// return domain.Transaction{ -// ID: transaction.ID, -// Amount: domain.Currency(transaction.Amount), -// Type: domain.TransactionType(transaction.TransactionType), -// Verified: transaction.Verified, -// WalletID: transaction.WalletID, -// } -// } +func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer { + return domain.Transfer{ + ID: transfer.ID, + Amount: domain.Currency(transfer.Amount), + Type: domain.TransferType(transfer.Type), + Verified: transfer.Verified, + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: domain.ValidInt64{ + Value: transfer.SenderWalletID.Int64, + Valid: transfer.SenderWalletID.Valid, + }, + CashierID: domain.ValidInt64{ + Value: transfer.CashierID.Int64, + Valid: transfer.CashierID.Valid, + }, + PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod), + } +} -// func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { -// return dbgen.CreateTransactionParams{ -// Amount: int64(transaction.Amount), -// TransactionType: string(transaction.Type), -// WalletID: transaction.WalletID, -// } -// } +func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams { + return dbgen.CreateTransferParams{ + Amount: int64(transfer.Amount), + Type: string(transfer.Type), + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: pgtype.Int8{ + Int64: transfer.SenderWalletID.Value, + Valid: transfer.SenderWalletID.Valid, + }, + CashierID: pgtype.Int8{ + Int64: transfer.CashierID.Value, + Valid: transfer.CashierID.Valid, + }, + PaymentMethod: string(transfer.PaymentMethod), + } +} -// func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { -// newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) -// if err != nil { -// return domain.Transaction{}, err -// } -// return convertDBTransaction(newTransaction), nil -// } +func (s *Store) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + newTransfer, err := s.queries.CreateTransfer(ctx, convertCreateTransfer(transfer)) + if err != nil { + return domain.Transfer{}, err + } + return convertDBTransfer(newTransfer), nil +} -// func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { -// transactions, err := s.queries.GetAllTransactions(ctx) -// if err != nil { -// return nil, err -// } -// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) +func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { + transfers, err := s.queries.GetAllTransfers(ctx) + if err != nil { + return nil, err + } + var result []domain.Transfer = make([]domain.Transfer, 0, len(transfers)) -// for _, transaction := range transactions { -// result = append(result, convertDBTransaction(transaction)) -// } -// return result, nil -// } -// func (s *Store) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) { -// transactions, err := s.queries.GetTransactionsByWallet(ctx, walletID) -// if err != nil { -// return nil, err -// } + for _, transfer := range transfers { + result = append(result, convertDBTransfer(transfer)) + } + return result, nil +} +func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { + transfers, err := s.queries.GetTransfersByWallet(ctx, walletID) + if err != nil { + return nil, err + } -// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) + var result []domain.Transfer = make([]domain.Transfer, 0, len(transfers)) -// for _, transaction := range transactions { -// result = append(result, convertDBTransaction(transaction)) -// } -// return result, nil -// } + for _, transfer := range transfers { + result = append(result, convertDBTransfer(transfer)) + } + return result, nil +} -// func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { -// transaction, err := s.queries.GetTransactionByID(ctx, id) -// if err != nil { -// return domain.Transaction{}, nil -// } -// return convertDBTransaction(transaction), nil -// } +func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { + transfer, err := s.queries.GetTransferByID(ctx, id) + if err != nil { + return domain.Transfer{}, nil + } + return convertDBTransfer(transfer), nil +} -// func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { -// err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ -// ID: id, -// Verified: verified, -// }) +func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ + ID: id, + Verified: verified, + }) -// return err -// } + return err +} diff --git a/internal/repository/user.go b/internal/repository/user.go index 2177b1e..1b20834 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -8,6 +8,7 @@ import ( dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/jackc/pgx/v5/pgtype" ) @@ -82,7 +83,7 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) Suspended: user.Suspended, }, nil } -func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) { +func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, error) { users, err := s.queries.GetAllUsers(ctx) if err != nil { return nil, err @@ -100,6 +101,68 @@ func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) { } return userList, nil } + +func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) { + users, err := s.queries.GetAllCashiers(ctx) + if err != nil { + return nil, err + } + userList := make([]domain.User, len(users)) + for i, user := range users { + userList[i] = domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + } + } + return userList, nil +} + +func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) { + users, err := s.queries.GetCashiersByBranch(ctx, branchID) + if err != nil { + return nil, err + } + userList := make([]domain.User, len(users)) + for i, user := range users { + userList[i] = domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + } + } + return userList, nil +} + +func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) { + users, err := s.queries.SearchUserByNameOrPhone(ctx, pgtype.Text{ + String: searchString, + Valid: true, + }) + if err != nil { + return nil, err + } + + userList := make([]domain.User, 0, len(users)) + for _, user := range users { + userList = append(userList, domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email.String, + PhoneNumber: user.PhoneNumber.String, + Role: domain.Role(user.Role), + }) + } + return userList, nil +} + func (s *Store) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ // ID: user.ID, @@ -209,3 +272,40 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password } return nil } +func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) { + userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ + FirstName: user.FirstName, + LastName: user.LastName, + Email: pgtype.Text{ + String: user.Email, + Valid: user.Email != "", + }, + PhoneNumber: pgtype.Text{ + String: user.PhoneNumber, + Valid: user.PhoneNumber != "", + }, + Password: user.Password, + Role: string(user.Role), + EmailVerified: user.EmailVerified, + PhoneVerified: user.PhoneVerified, + CreatedAt: pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + }, + UpdatedAt: pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + }, + }) + if err != nil { + return domain.User{}, err + } + return domain.User{ + ID: userRes.ID, + FirstName: userRes.FirstName, + LastName: userRes.LastName, + Email: userRes.Email.String, + PhoneNumber: userRes.PhoneNumber.String, + Role: domain.Role(userRes.Role), + }, nil +} diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 87f88ac..86bf670 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -9,22 +9,24 @@ import ( func convertDBWallet(wallet dbgen.Wallet) domain.Wallet { return domain.Wallet{ - ID: wallet.ID, - Balance: domain.Currency(wallet.Balance), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt.Time, - CreatedAt: wallet.CreatedAt.Time, + ID: wallet.ID, + Balance: domain.Currency(wallet.Balance), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt.Time, + CreatedAt: wallet.CreatedAt.Time, } } func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams { return dbgen.CreateWalletParams{ - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - UserID: wallet.UserID, + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + UserID: wallet.UserID, } } @@ -93,7 +95,7 @@ func (s *Store) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { return nil, err } - var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + var result []domain.Wallet = make([]domain.Wallet, 0, len(wallets)) for _, wallet := range wallets { result = append(result, convertDBWallet(wallet)) @@ -107,7 +109,7 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa return nil, err } - var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + var result []domain.Wallet = make([]domain.Wallet, 0, len(wallets)) for _, wallet := range wallets { result = append(result, convertDBWallet(wallet)) @@ -127,6 +129,31 @@ func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64, company return convertDBGetCustomerWallet(customerWallet), nil } +func (s *Store) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) { + wallets, err := s.queries.GetAllBranchWallets(ctx) + if err != nil { + return nil, err + } + + var result []domain.BranchWallet = make([]domain.BranchWallet, 0, len(wallets)) + + for _, wallet := range wallets { + result = append(result, domain.BranchWallet{ + ID: wallet.ID, + Balance: domain.Currency(wallet.Balance), + IsActive: wallet.IsActive, + UpdatedAt: wallet.UpdatedAt.Time, + CreatedAt: wallet.CreatedAt.Time, + Name: wallet.Name, + Location: wallet.Location, + BranchManagerID: wallet.BranchManagerID, + CompanyID: wallet.CompanyID, + IsSelfOwned: wallet.IsSelfOwned, + }) + } + return result, nil +} + func (s *Store) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { err := s.queries.UpdateBalance(ctx, dbgen.UpdateBalanceParams{ ID: id, diff --git a/internal/services/authentication/impl.go b/internal/services/authentication/impl.go index ea8de4d..2d6bb0b 100644 --- a/internal/services/authentication/impl.go +++ b/internal/services/authentication/impl.go @@ -19,9 +19,10 @@ var ( ) type LoginSuccess struct { - UserId int64 - Role domain.Role - RfToken string + UserId int64 + Role domain.Role + RfToken string + BranchId int64 } func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) { @@ -48,40 +49,38 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin return LoginSuccess{}, err } return LoginSuccess{ - UserId: user.ID, - Role: user.Role, - RfToken: refreshToken, + UserId: user.ID, + Role: user.Role, + RfToken: refreshToken, + BranchId: user.BranchID, }, nil } -func (s *Service) RefreshToken(ctx context.Context, refToken string) (string, error) { +func (s *Service) RefreshToken(ctx context.Context, refToken string) error { token, err := s.tokenStore.GetRefreshToken(ctx, refToken) if err != nil { - return "", err + return err } if token.Revoked { - return "", ErrRefreshTokenNotFound + return ErrRefreshTokenNotFound } if token.ExpiresAt.Before(time.Now()) { - return "", ErrExpiredToken + return ErrExpiredToken } - newRefToken, err := generateRefreshToken() - if err != nil { - return "", err - } + // newRefToken, err := generateRefreshToken() + // if err != nil { + // return "", err + // } - err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{ - Token: newRefToken, - UserID: token.UserID, - CreatedAt: time.Now(), - ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), - }) - if err != nil { - return "", err - } - return newRefToken, nil + // err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{ + // Token: newRefToken, + // UserID: token.UserID, + // CreatedAt: time.Now(), + // ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), + // }) + return nil } func (s *Service) Logout(ctx context.Context, refToken string) error { token, err := s.tokenStore.GetRefreshToken(ctx, refToken) diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 1061b45..d5ea609 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -8,8 +8,13 @@ import ( type BetStore interface { CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) - GetBetByID(ctx context.Context, id int64) (domain.Bet, error) - GetAllBets(ctx context.Context) ([]domain.Bet, error) + CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) + GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) + GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) + GetAllBets(ctx context.Context) ([]domain.GetBet, error) + GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error + UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error + UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error DeleteBet(ctx context.Context, id int64) error } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 58b9cc5..1ff9565 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -17,19 +17,40 @@ func NewService(betStore BetStore) *Service { } func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { + return s.betStore.CreateBet(ctx, bet) } -func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.Bet, error) { + +func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) { + return s.betStore.CreateBetOutcome(ctx, outcomes) +} + +func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { return s.betStore.GetBetByID(ctx, id) } -func (s *Service) GetAllBets(ctx context.Context) ([]domain.Bet, error) { +func (s *Service) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) { + return s.betStore.GetBetByCashoutID(ctx, id) +} +func (s *Service) GetAllBets(ctx context.Context) ([]domain.GetBet, error) { return s.betStore.GetAllBets(ctx) } +func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domain.GetBet, error) { + return s.betStore.GetBetByBranchID(ctx, branchID) +} + func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } +func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + return s.betStore.UpdateStatus(ctx, id, status) +} + +func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + return s.betStore.UpdateBetOutcomeStatus(ctx, id, status) +} + func (s *Service) DeleteBet(ctx context.Context, id int64) error { return s.betStore.DeleteBet(ctx, id) } diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go new file mode 100644 index 0000000..53e6b0d --- /dev/null +++ b/internal/services/branch/port.go @@ -0,0 +1,26 @@ +package branch + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type BranchStore interface { + CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) + GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) + GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) + GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) + GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) + SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) + UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) + DeleteBranch(ctx context.Context, id int64) error + CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error + CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) + GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) + GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) + DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error + CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error + GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) + DeleteBranchCashier(ctx context.Context, userID int64) error +} diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go new file mode 100644 index 0000000..eddd1e9 --- /dev/null +++ b/internal/services/branch/service.go @@ -0,0 +1,72 @@ +package branch + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + branchStore BranchStore +} + +func NewService(branchStore BranchStore) *Service { + return &Service{ + branchStore: branchStore, + } +} + +func (s *Service) CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) { + return s.branchStore.CreateBranch(ctx, branch) +} +func (s *Service) CreateSupportedOperation(ctx context.Context, supportedOperation domain.CreateSupportedOperation) (domain.SupportedOperation, error) { + return s.branchStore.CreateSupportedOperation(ctx, supportedOperation) +} +func (s *Service) CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error { + return s.branchStore.CreateBranchOperation(ctx, branchOperation) +} + +func (s *Service) CreateBranchCashier(ctx context.Context, branchID int64, userID int64) error { + return s.branchStore.CreateBranchCashier(ctx, branchID, userID) +} + +func (s *Service) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { + return s.branchStore.GetBranchByID(ctx, id) +} +func (s *Service) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) { + return s.branchStore.GetBranchByManagerID(ctx, branchManagerID) +} +func (s *Service) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) { + return s.branchStore.GetBranchByCompanyID(ctx, companyID) +} +func (s *Service) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) { + return s.branchStore.GetBranchOperations(ctx, branchID) +} +func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) { + return s.branchStore.GetAllBranches(ctx) +} + +func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) { + return s.branchStore.GetBranchByCashier(ctx, userID) +} + +func (s *Service) GetAllSupportedOperations(ctx context.Context) ([]domain.SupportedOperation, error) { + return s.branchStore.GetAllSupportedOperations(ctx) +} + +func (s *Service) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { + return s.branchStore.SearchBranchByName(ctx, name) +} +func (s *Service) UpdateBranch(ctx context.Context, id int64, branch domain.UpdateBranch) (domain.Branch, error) { + return s.branchStore.UpdateBranch(ctx, id, branch) +} +func (s *Service) DeleteBranch(ctx context.Context, id int64) error { + return s.branchStore.DeleteBranch(ctx, id) +} +func (s *Service) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { + return s.branchStore.DeleteBranchOperation(ctx, branchID, operationID) +} + +func (s *Service) DeleteBranchCashier(ctx context.Context, userID int64) error { + return s.branchStore.DeleteBranchCashier(ctx, userID) +} diff --git a/internal/services/company/port.go b/internal/services/company/port.go new file mode 100644 index 0000000..5263ed1 --- /dev/null +++ b/internal/services/company/port.go @@ -0,0 +1,15 @@ +package company + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type CompanyStore interface { + CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) + GetAllCompanies(ctx context.Context) ([]domain.Company, error) + GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) + UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) + DeleteCompany(ctx context.Context, id int64) error +} diff --git a/internal/services/company/service.go b/internal/services/company/service.go new file mode 100644 index 0000000..b5b6f7e --- /dev/null +++ b/internal/services/company/service.go @@ -0,0 +1,35 @@ +package company + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + companyStore CompanyStore +} + +func NewService(companyStore CompanyStore) *Service { + return &Service{ + companyStore: companyStore, + } +} + +func (s *Service) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { + return s.companyStore.CreateCompany(ctx, company) +} +func (s *Service) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { + return s.companyStore.GetAllCompanies(ctx) +} + +func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { + return s.companyStore.GetCompanyByID(ctx, id) +} + +func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { + return s.companyStore.UpdateCompany(ctx, id, company) +} +func (s *Service) DeleteCompany(ctx context.Context, id int64) error { + return s.companyStore.DeleteCompany(ctx, id) +} diff --git a/internal/services/event/port.go b/internal/services/event/port.go new file mode 100644 index 0000000..1404b09 --- /dev/null +++ b/internal/services/event/port.go @@ -0,0 +1,15 @@ +package event + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service interface { + FetchLiveEvents(ctx context.Context) error + FetchUpcomingEvents(ctx context.Context) error + GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) + GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) + GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) +} diff --git a/internal/services/event/service.go b/internal/services/event/service.go new file mode 100644 index 0000000..4eb5601 --- /dev/null +++ b/internal/services/event/service.go @@ -0,0 +1,187 @@ +package event + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "sync" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" + // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" +) + +type service struct { + token string + store *repository.Store +} + +func New(token string, store *repository.Store) Service { + return &service{ + token: token, + store: store, + } +} + +func (s *service) FetchLiveEvents(ctx context.Context) error { + sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148} + + var wg sync.WaitGroup + + for _, sportID := range sportIDs { + wg.Add(1) + go func(sportID int) { + defer wg.Done() + + url := fmt.Sprintf("https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", sportID, s.token) + resp, err := http.Get(url) + if err != nil { + fmt.Printf(" Failed request for sport_id=%d: %v\n", sportID, err) + return + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + var data struct { + Success int `json:"success"` + Results [][]map[string]interface{} `json:"results"` + } + if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { + fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body)) + return + } + + for _, group := range data.Results { + for _, ev := range group { + if getString(ev["type"]) != "EV" { + continue + } + + event := domain.Event{ + ID: getString(ev["ID"]), + SportID: fmt.Sprintf("%d", sportID), + MatchName: getString(ev["NA"]), + Score: getString(ev["SS"]), + MatchMinute: getInt(ev["TM"]), + TimerStatus: getString(ev["TT"]), + HomeTeamID: getString(ev["HT"]), + AwayTeamID: getString(ev["AT"]), + HomeKitImage: getString(ev["K1"]), + AwayKitImage: getString(ev["K2"]), + LeagueName: getString(ev["CT"]), + LeagueID: getString(ev["C2"]), + LeagueCC: getString(ev["CB"]), + StartTime: time.Now().UTC().Format(time.RFC3339), + IsLive: true, + Status: "live", + MatchPeriod: getInt(ev["MD"]), + AddedTime: getInt(ev["TA"]), + } + + if err := s.store.SaveEvent(ctx, event); err != nil { + fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err) + } + } + } + }(sportID) + } + + wg.Wait() + fmt.Println("All live events fetched and stored.") + return nil +} + +func (s *service) FetchUpcomingEvents(ctx context.Context) error { + sportIDs := []int{1} + for _, sportID := range sportIDs { + url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) + resp, err := http.Get(url) + if err != nil { + continue + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + var data struct { + Success int `json:"success"` + Results []struct { + ID string `json:"id"` + SportID string `json:"sport_id"` + Time string `json:"time"` + League struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"league"` + Home struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"home"` + Away *struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"away"` + } `json:"results"` + } + if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { + continue + } + + for _, ev := range data.Results { + startUnix, _ := strconv.ParseInt(ev.Time, 10, 64) + event := domain.UpcomingEvent{ + ID: ev.ID, + SportID: ev.SportID, + MatchName: ev.Home.Name, + HomeTeam: ev.Home.Name, + AwayTeam: "", // handle nil safely + HomeTeamID: ev.Home.ID, + AwayTeamID: "", + HomeKitImage: "", + AwayKitImage: "", + LeagueID: ev.League.ID, + LeagueName: ev.League.Name, + LeagueCC: "", + StartTime: time.Unix(startUnix, 0).UTC(), + } + + if ev.Away != nil { + event.AwayTeam = ev.Away.Name + event.AwayTeamID = ev.Away.ID + } + + _ = s.store.SaveUpcomingEvent(ctx, event) + } + } + + return nil +} + +func getString(v interface{}) string { + if str, ok := v.(string); ok { + return str + } + return "" +} + +func getInt(v interface{}) int { + if f, ok := v.(float64); ok { + return int(f) + } + return 0 +} +func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { + return s.store.GetAllUpcomingEvents(ctx) +} + +func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) { + return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID) +} + +func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { + return s.store.GetUpcomingEventByID(ctx, ID) +} diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go new file mode 100644 index 0000000..69fd5ee --- /dev/null +++ b/internal/services/odds/port.go @@ -0,0 +1,14 @@ +package odds + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service interface { + FetchNonLiveOdds(ctx context.Context) error + GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) + GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) + GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) +} diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go new file mode 100644 index 0000000..aa59a5a --- /dev/null +++ b/internal/services/odds/service.go @@ -0,0 +1,133 @@ +package odds + +import ( + "context" + "encoding/json" + "io" + "log" + "net/http" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" +) + +type ServiceImpl struct { + token string + store *repository.Store +} + +func New(token string, store *repository.Store) *ServiceImpl { + return &ServiceImpl{token: token, store: store} +} + +func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { + eventIDs, err := s.store.GetAllUpcomingEvents(ctx) + if err != nil { + log.Printf("❌ Failed to fetch upcoming event IDs: %v", err) + return err + } + + for _, event := range eventIDs { + eventID := event.ID + prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID + log.Printf("📡 Fetching prematch odds for event ID: %s", eventID) + resp, err := http.Get(prematchURL) + if err != nil { + log.Printf("❌ Failed to fetch prematch odds for event %s: %v", eventID, err) + continue + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + var oddsData struct { + Success int `json:"success"` + Results []struct { + EventID string `json:"event_id"` + FI string `json:"FI"` + Main OddsSection `json:"main"` + } `json:"results"` + } + if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 { + log.Printf("❌ Invalid prematch data for event %s", eventID) + continue + } + + result := oddsData.Results[0] + finalID := result.EventID + if finalID == "" { + finalID = result.FI + } + if finalID == "" { + log.Printf("⚠️ Skipping event %s with no valid ID", eventID) + continue + } + + s.storeSection(ctx, finalID, result.FI, "main", result.Main) + } + + return nil +} + +func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) { + if len(section.Sp) == 0 { + return + } + + updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64) + updatedAt := time.Unix(updatedAtUnix, 0) + + for marketType, market := range section.Sp { + if len(market.Odds) == 0 { + continue + } + + marketRecord := domain.Market{ + EventID: eventID, + FI: fi, + MarketCategory: sectionName, + MarketType: marketType, + MarketName: market.Name, + MarketID: market.ID, + UpdatedAt: updatedAt, + Odds: market.Odds, + } + + _ = s.store.SaveNonLiveMarket(ctx, marketRecord) + } +} + +type OddsMarket struct { + ID string `json:"id"` + Name string `json:"name"` + Odds []json.RawMessage `json:"odds"` + Header string `json:"header,omitempty"` + Handicap string `json:"handicap,omitempty"` +} + +type OddsSection struct { + UpdatedAt string `json:"updated_at"` + Sp map[string]OddsMarket `json:"sp"` +} + +func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { + return s.store.GetPrematchOdds(ctx, eventID) +} + +func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { + return s.store.GetALLPrematchOdds(ctx) +} + +func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) { + rows, err := s.store.GetRawOddsByMarketID(ctx, marketID, upcomingID) + if err != nil { + return domain.RawOddsByMarketID{}, err + } + + return rows, nil +} + +func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) { + return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset) +} diff --git a/internal/services/ticket/port.go b/internal/services/ticket/port.go index 042d27a..930026e 100644 --- a/internal/services/ticket/port.go +++ b/internal/services/ticket/port.go @@ -8,8 +8,10 @@ import ( type TicketStore interface { CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) - GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) - GetAllTickets(ctx context.Context) ([]domain.Ticket, error) + CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) + GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) + GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) + UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error DeleteOldTickets(ctx context.Context) error DeleteTicket(ctx context.Context, id int64) error } diff --git a/internal/services/ticket/service.go b/internal/services/ticket/service.go index 5779ce4..1d86313 100644 --- a/internal/services/ticket/service.go +++ b/internal/services/ticket/service.go @@ -19,12 +19,21 @@ func NewService(ticketStore TicketStore) *Service { func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) { return s.ticketStore.CreateTicket(ctx, ticket) } -func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.Ticket, error) { + +func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) { + return s.ticketStore.CreateTicketOutcome(ctx, outcomes) +} + +func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) { return s.ticketStore.GetTicketByID(ctx, id) } -func (s *Service) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { +func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { return s.ticketStore.GetAllTickets(ctx) } + +func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { + return s.ticketStore.UpdateTicketOutcomeStatus(ctx, id, status) +} func (s *Service) DeleteTicket(ctx context.Context, id int64) error { return s.ticketStore.DeleteTicket(ctx, id) } diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go index 27b9f5a..cbd9a0f 100644 --- a/internal/services/transaction/port.go +++ b/internal/services/transaction/port.go @@ -10,5 +10,6 @@ type TransactionStore interface { CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) + GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error } diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 31ca58e..2c33917 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -25,6 +25,9 @@ func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Tran func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { return s.transactionStore.GetAllTransactions(ctx) } +func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { + return s.transactionStore.GetTransactionByBranch(ctx, id) +} func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { return s.transactionStore.UpdateTransactionVerified(ctx, id, verified) diff --git a/internal/services/transfer/chapa.go b/internal/services/transfer/chapa.go deleted file mode 100644 index 3e884d3..0000000 --- a/internal/services/transfer/chapa.go +++ /dev/null @@ -1 +0,0 @@ -package transfer diff --git a/internal/services/transfer/port.go b/internal/services/transfer/port.go deleted file mode 100644 index 213d65a..0000000 --- a/internal/services/transfer/port.go +++ /dev/null @@ -1,15 +0,0 @@ -package transfer - -import ( - "context" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - -type TransferStore interface { - CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) - GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) - GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) - GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) - UpdateTransferVerification(ctx context.Context, id int64, verified bool) error -} diff --git a/internal/services/transfer/service.go b/internal/services/transfer/service.go deleted file mode 100644 index c628c42..0000000 --- a/internal/services/transfer/service.go +++ /dev/null @@ -1,33 +0,0 @@ -package transfer - -import ( - "context" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - -type Service struct { - transferStore TransferStore -} - -func NewService(transferStore TransferStore) *Service { - return &Service{ - transferStore: transferStore, - } -} - -func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { - return s.transferStore.CreateTransfer(ctx, transfer) -} - -func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { - return s.transferStore.GetAllTransfers(ctx) -} - -func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { - return s.transferStore.GetTransferByID(ctx, id) -} - -func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { - return s.transferStore.UpdateTransferVerification(ctx, id, verified) -} diff --git a/internal/services/user/direct.go b/internal/services/user/direct.go new file mode 100644 index 0000000..f848792 --- /dev/null +++ b/internal/services/user/direct.go @@ -0,0 +1,74 @@ +package user + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq) (domain.User, error) { + // Create User + // creator, err := s.userStore.GetUserByID(ctx, createrUserId) + // if err != nil { + // return domain.User{}, err + // } + // if creator.Role != domain.RoleAdmin { + // User.BranchID = creator.BranchID + // User.Role = string(domain.RoleCashier) + // } else { + // User.BranchID = branchId + // User.Role = string(domain.RoleBranchManager) + // } + hashedPassword, err := hashPassword(User.Password) + if err != nil { + return domain.User{}, err + } + + return s.userStore.CreateUserWithoutOtp(ctx, domain.User{ + FirstName: User.FirstName, + LastName: User.LastName, + Email: User.Email, + PhoneNumber: User.PhoneNumber, + Password: hashedPassword, + Role: domain.Role(User.Role), + EmailVerified: true, + PhoneVerified: true, + }) +} + +func (s *Service) DeleteUser(ctx context.Context, id int64) error { + // Delete User + return s.userStore.DeleteUser(ctx, id) +} + +type Filter struct { + Role string + BranchId ValidBranchId + Page int + PageSize int +} +type ValidRole struct { + Value domain.Role + Valid bool +} +type ValidBranchId struct { + Value int64 + Valid bool +} + +func (s *Service) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, error) { + // Get all Users + return s.userStore.GetAllUsers(ctx, filter) +} +func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) { + + return s.userStore.GetUserByID(ctx, id) +} + +func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) { + return s.userStore.GetCashiersByBranch(ctx, branchID) +} + +func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.User, error) { + return s.userStore.GetAllCashiers(ctx) +} diff --git a/internal/services/user/port.go b/internal/services/user/port.go index aaf502d..9b8d06e 100644 --- a/internal/services/user/port.go +++ b/internal/services/user/port.go @@ -8,14 +8,17 @@ import ( type UserStore interface { CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) + CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error) - GetAllUsers(ctx context.Context) ([]domain.User, error) + GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, error) + GetAllCashiers(ctx context.Context) ([]domain.User, error) + GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error DeleteUser(ctx context.Context, id int64) error CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) GetUserByEmail(ctx context.Context, email string) (domain.User, error) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) - // + SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error // identifier verified email or phone } type SmsGateway interface { diff --git a/internal/services/user/register.go b/internal/services/user/register.go index 7966254..dcefa99 100644 --- a/internal/services/user/register.go +++ b/internal/services/user/register.go @@ -65,7 +65,7 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU Email: registerReq.Email, PhoneNumber: registerReq.PhoneNumber, Password: hashedPassword, - Role: "user", + Role: domain.RoleCustomer, EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail, PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms, } diff --git a/internal/services/user/user.go b/internal/services/user/user.go index 5b65b94..5c141c7 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -6,6 +6,14 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) + + + +func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) { + // Search user + return s.userStore.SearchUserByNameOrPhone(ctx, searchString) + +} func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { // update user return s.userStore.UpdateUser(ctx, user) diff --git a/internal/services/wallet/chapa.go b/internal/services/wallet/chapa.go new file mode 100644 index 0000000..23a7507 --- /dev/null +++ b/internal/services/wallet/chapa.go @@ -0,0 +1 @@ +package wallet diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index 0e369bd..9271039 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -13,6 +13,15 @@ type WalletStore interface { GetAllWallets(ctx context.Context) ([]domain.Wallet, error) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) + GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error UpdateWalletActive(ctx context.Context, id int64, isActive bool) error } + +type TransferStore interface { + CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) + GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) + GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) + GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) + UpdateTransferVerification(ctx context.Context, id int64, verified bool) error +} diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 1cb9ebe..a8913c2 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -1,102 +1,13 @@ package wallet -import ( - "context" - "errors" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" -) - type Service struct { - walletStore WalletStore + walletStore WalletStore + transferStore TransferStore } -func NewService(walletStore WalletStore) *Service { +func NewService(walletStore WalletStore, transferStore TransferStore) *Service { return &Service{ - walletStore: walletStore, + walletStore: walletStore, + transferStore: transferStore, } } - -var ( - ErrBalanceInsufficient = errors.New("wallet balance is insufficient") -) - -func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { - return s.walletStore.CreateWallet(ctx, wallet) -} - -func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) { - - regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ - IsWithdraw: true, - IsBettable: true, - UserID: customerID, - }) - - if err != nil { - return domain.CustomerWallet{}, err - } - - staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ - IsWithdraw: false, - IsBettable: true, - UserID: customerID, - }) - - if err != nil { - return domain.CustomerWallet{}, err - } - - return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ - CustomerID: customerID, - CompanyID: companyID, - RegularWalletID: regularWallet.ID, - StaticWalletID: staticWallet.ID, - }) -} - -func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { - return s.walletStore.GetWalletByID(ctx, id) -} - -func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { - return s.walletStore.GetAllWallets(ctx) -} - -func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { - return s.walletStore.GetWalletsByUser(ctx, id) -} - -func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { - return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) -} - -func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { - return s.walletStore.UpdateBalance(ctx, id, balance) -} - -func (s *Service) Add(ctx context.Context, id int64, amount domain.Currency) error { - wallet, err := s.GetWalletByID(ctx, id) - if err != nil { - return err - } - - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) -} - -func (s *Service) Deduct(ctx context.Context, id int64, amount domain.Currency) error { - wallet, err := s.GetWalletByID(ctx, id) - if err != nil { - return err - } - - if wallet.Balance < amount { - return ErrBalanceInsufficient - } - - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) -} - -func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { - return s.walletStore.UpdateWalletActive(ctx, id, isActive) -} diff --git a/internal/services/wallet/transfer.go b/internal/services/wallet/transfer.go new file mode 100644 index 0000000..387f255 --- /dev/null +++ b/internal/services/wallet/transfer.go @@ -0,0 +1,119 @@ +package wallet + +import ( + "context" + "errors" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +var ( + ErrWalletNotTransferable = errors.New("wallet is not transferable") +) + +func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + return s.transferStore.CreateTransfer(ctx, transfer) +} + +func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { + return s.transferStore.GetAllTransfers(ctx) +} + +func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { + return s.transferStore.GetTransferByID(ctx, id) +} + +func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { + return s.transferStore.GetTransfersByWallet(ctx, walletID) +} + +func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + return s.transferStore.UpdateTransferVerification(ctx, id, verified) +} + +func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID) + if err != nil { + return domain.Transfer{}, err + } + + // Add to receiver + err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount) + if err != nil { + return domain.Transfer{}, err + } + + // Log the transfer so that if there is a mistake, it can be reverted + newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + CashierID: transfer.CashierID, + ReceiverWalletID: receiverWallet.ID, + Amount: transfer.Amount, + Type: domain.DEPOSIT, + PaymentMethod: transfer.PaymentMethod, + Verified: true, + }) + if err != nil { + return domain.Transfer{}, err + } + + return newTransfer, nil + +} + +func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64) (domain.Transfer, error) { + + senderWallet, err := s.GetWalletByID(ctx, senderID) + if err != nil { + return domain.Transfer{}, err + } + + if senderWallet.IsTransferable { + return domain.Transfer{}, ErrWalletNotTransferable + } + + receiverWallet, err := s.GetWalletByID(ctx, receiverID) + if err != nil { + return domain.Transfer{}, err + } + + if receiverWallet.IsTransferable { + return domain.Transfer{}, ErrWalletNotTransferable + } + + // Deduct from sender + if senderWallet.Balance < amount { + return domain.Transfer{}, ErrBalanceInsufficient + } + + err = s.walletStore.UpdateBalance(ctx, senderID, senderWallet.Balance-amount) + + if err != nil { + return domain.Transfer{}, err + } + + // Add to receiver + err = s.walletStore.UpdateBalance(ctx, receiverID, receiverWallet.Balance+amount) + + if err != nil { + return domain.Transfer{}, err + } + + // Log the transfer so that if there is a mistake, it can be reverted + transfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ + SenderWalletID: domain.ValidInt64{ + Value: senderID, + Valid: true, + }, + CashierID: cashierID, + ReceiverWalletID: receiverID, + Amount: amount, + Type: domain.WALLET, + PaymentMethod: paymentMethod, + Verified: true, + }) + if err != nil { + return domain.Transfer{}, err + } + + return transfer, nil +} diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go new file mode 100644 index 0000000..ced664d --- /dev/null +++ b/internal/services/wallet/wallet.go @@ -0,0 +1,98 @@ +package wallet + +import ( + "context" + "errors" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +var ( + ErrBalanceInsufficient = errors.New("wallet balance is insufficient") +) + +func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { + return s.walletStore.CreateWallet(ctx, wallet) +} + +func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) { + + regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: true, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ + CustomerID: customerID, + CompanyID: companyID, + RegularWalletID: regularWallet.ID, + StaticWalletID: staticWallet.ID, + }) +} + +func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { + return s.walletStore.GetWalletByID(ctx, id) +} + +func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { + return s.walletStore.GetAllWallets(ctx) +} + +func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { + return s.walletStore.GetWalletsByUser(ctx, id) +} + +func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { + return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) +} + +func (s *Service) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) { + return s.walletStore.GetAllBranchWallets(ctx) +} + +func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { + return s.walletStore.UpdateBalance(ctx, id, balance) +} + +func (s *Service) AddToWallet(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + +func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + if wallet.Balance < amount { + return ErrBalanceInsufficient + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + + + +func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { + return s.walletStore.UpdateWalletActive(ctx, id, isActive) +} diff --git a/internal/web_server/app.go b/internal/web_server/app.go index e44e602..3864b2f 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -7,6 +7,10 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -18,6 +22,7 @@ import ( notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/bytedance/sonic" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" ) type App struct { @@ -33,9 +38,13 @@ type App struct { walletSvc *wallet.Service transactionSvc *transaction.Service ticketSvc *ticket.Service + branchSvc *branch.Service + companySvc *company.Service validator *customvalidator.CustomValidator JwtConfig jwtutil.JwtConfig Logger *slog.Logger + prematchSvc *odds.ServiceImpl + eventSvc event.Service } func NewApp( @@ -48,7 +57,11 @@ func NewApp( betSvc *bet.Service, walletSvc *wallet.Service, transactionSvc *transaction.Service, + branchSvc *branch.Service, + companySvc *company.Service, notidicationStore notificationservice.NotificationStore, + prematchSvc *odds.ServiceImpl, + eventSvc event.Service, referralSvc referralservice.ReferralStore, virtualGameSvc virtualgameservice.VirtualGameService, ) *App { @@ -58,6 +71,14 @@ func NewApp( JSONEncoder: sonic.Marshal, JSONDecoder: sonic.Unmarshal, }) + + app.Use(cors.New(cors.Config{ + AllowOrigins: "*", // Specify your frontend's origin + AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", // Specify the allowed HTTP methods + AllowHeaders: "Content-Type,Authorization,platform", // Specify the allowed headers + // AllowCredentials: true, + })) + s := &App{ fiber: app, port: port, @@ -70,9 +91,13 @@ func NewApp( betSvc: betSvc, walletSvc: walletSvc, transactionSvc: transactionSvc, + branchSvc: branchSvc, + companySvc: companySvc, NotidicationStore: notidicationStore, referralSvc: referralSvc, Logger: logger, + prematchSvc: prematchSvc, + eventSvc: eventSvc, virtualGameSvc: virtualGameSvc, } diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go new file mode 100644 index 0000000..3106cb8 --- /dev/null +++ b/internal/web_server/cron.go @@ -0,0 +1,58 @@ +package httpserver + +import ( + "fmt" + "log" + + eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" + oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/robfig/cron/v3" +) + +func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service) { + c := cron.New(cron.WithSeconds()) + + schedule := []struct { + spec string + task func() + }{ + + // { + // spec: "0 0 * * * *", // Every hour + // task: func() { + // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + // log.Printf("FetchUpcomingEvents error: %v", err) + // } + // }, + // }, + + // { + // spec: "*/5 * * * * *", // Every 5 seconds + // task: func() { + // if err := eventService.FetchLiveEvents(context.Background()); err != nil { + // log.Printf("FetchLiveEvents error: %v", err) + // } + // }, + // }, + + // { + // spec: "0 */15 * * * *", // Every 15 minutes + // task: func() { + // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + // log.Printf("FetchNonLiveOdds error: %v", err) + // } + // }, + // }, + } + + for _, job := range schedule { + job.task() + fmt.Printf("here at") + if _, err := c.AddFunc(job.spec, job.task); err != nil { + log.Fatalf("Failed to schedule cron job: %v", err) + } + } + + c.Start() + log.Println("Cron jobs started for event and odds services") +} diff --git a/internal/web_server/handlers/auth_handler.go b/internal/web_server/handlers/auth_handler.go index 88ca2f1..2e094fd 100644 --- a/internal/web_server/handlers/auth_handler.go +++ b/internal/web_server/handlers/auth_handler.go @@ -3,12 +3,14 @@ package handlers import ( "errors" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" ) + // LoginCustomer godoc // @Summary Login customer // @Description Login customer @@ -30,6 +32,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error { type loginCustomerRes struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` + Role string `json:"role"` } var req loginCustomerReq @@ -54,7 +57,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error { } } - accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) + accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.BranchId, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) if err != nil { h.logger.Error("Failed to create access token", "userID", successRes.UserId, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") @@ -63,10 +66,12 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error { res := loginCustomerRes{ AccessToken: accessToken, RefreshToken: successRes.RfToken, + Role: string(successRes.Role), } return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil) } + // RefreshToken godoc // @Summary Refresh token // @Description Refresh token @@ -99,7 +104,10 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - rf, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken) + userId := c.Locals("user_id").(int64) + role := c.Locals("role").(string) + branchId := c.Locals("branch_id").(int64) + err := authSvc.RefreshToken(c.Context(), req.RefreshToken) if err != nil { h.logger.Info("Refresh token attempt failed", "refreshToken", req.RefreshToken, "error", err) switch { @@ -114,7 +122,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error { } // Assuming the refreshed token includes userID and role info; adjust if needed - accessToken, err := jwtutil.CreateJwt(0, "", h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) + accessToken, err := jwtutil.CreateJwt(userId, domain.Role(role), branchId, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) if err != nil { h.logger.Error("Failed to create new access token", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") @@ -122,7 +130,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error { res := loginCustomerRes{ AccessToken: accessToken, - RefreshToken: rf, + RefreshToken: req.RefreshToken, } return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 4492714..9e50490 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -1,24 +1,93 @@ package handlers import ( + "encoding/json" + "log/slog" "strconv" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" "github.com/gofiber/fiber/v2" + "github.com/google/uuid" ) +type CreateBetOutcomeReq struct { + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` + MarketID int64 `json:"market_id" example:"1"` +} + +type CreateBetReq struct { + Outcomes []CreateBetOutcomeReq `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status domain.OutcomeStatus `json:"status" example:"1"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + IsShopBet bool `json:"is_shop_bet" example:"false"` +} + +type CreateBetRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status domain.OutcomeStatus `json:"status" example:"1"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + BranchID int64 `json:"branch_id" example:"2"` + UserID int64 `json:"user_id" example:"2"` + IsShopBet bool `json:"is_shop_bet" example:"false"` + CreatedNumber int64 `json:"created_number" example:"2"` + CashedID string `json:"cashed_id" example:"21234"` +} type BetRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []domain.Outcome `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` - Status domain.BetStatus `json:"status" example:"1"` - FullName string `json:"full_name" example:"John"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - BranchID int64 `json:"branch_id" example:"2"` - UserID int64 `json:"user_id" example:"2"` - IsShopBet bool `json:"is_shop_bet" example:"false"` + ID int64 `json:"id" example:"1"` + Outcomes []domain.BetOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status domain.OutcomeStatus `json:"status" example:"1"` + FullName string `json:"full_name" example:"John"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + BranchID int64 `json:"branch_id" example:"2"` + UserID int64 `json:"user_id" example:"2"` + IsShopBet bool `json:"is_shop_bet" example:"false"` + CashedOut bool `json:"cashed_out" example:"false"` + CashedID string `json:"cashed_id" example:"21234"` +} + +func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes { + return CreateBetRes{ + ID: bet.ID, + Amount: bet.Amount.Float64(), + TotalOdds: bet.TotalOdds, + Status: bet.Status, + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + BranchID: bet.BranchID.Value, + UserID: bet.UserID.Value, + CreatedNumber: createdNumber, + CashedID: bet.CashoutID, + } +} + +func convertBet(bet domain.GetBet) BetRes { + return BetRes{ + ID: bet.ID, + Amount: bet.Amount.Float64(), + TotalOdds: bet.TotalOdds, + Status: bet.Status, + FullName: bet.FullName, + PhoneNumber: bet.PhoneNumber, + BranchID: bet.BranchID.Value, + UserID: bet.UserID.Value, + Outcomes: bet.Outcomes, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + CashedID: bet.CashoutID, + } } // CreateBet godoc @@ -33,15 +102,9 @@ type BetRes struct { // @Failure 500 {object} response.APIResponse // @Router /bet [post] func (h *Handler) CreateBet(c *fiber.Ctx) error { - type CreateBetReq struct { - Outcomes []int64 `json:"outcomes" validate:"required" example:"[1, 2, 3]"` - Amount float32 `json:"amount" validate:"required" example:"100.0"` - TotalOdds float32 `json:"total_odds" validate:"required" example:"4.22"` - Status domain.BetStatus `json:"status" validate:"required" example:"1"` - FullName string `json:"full_name" example:"John"` - PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"` - IsShopBet bool `json:"is_shop_bet" example:"false"` - } + + // Get user_id from middleware + userID := c.Locals("user_id").(int64) var req CreateBetReq if err := c.BodyParser(&req); err != nil { @@ -49,50 +112,165 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - if valErrs, ok := h.validator.Validate(c, req); !ok { + valErrs, ok := h.validator.Validate(c, req) + if !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - // TODO: Check the token, find the role, and get the branch ID from there - isShopBet := true - branchID := int64(1) - var userID int64 + // Validating user by role + // Differentiating between offline and online bets + user, err := h.userSvc.GetUserByID(c.Context(), userID) + cashoutUUID := uuid.New() + var bet domain.Bet + if user.Role == domain.RoleCashier { - // TODO: Validate Outcomes Here and make sure they didn't expire - - bet, err := h.betSvc.CreateBet(c.Context(), domain.CreateBet{ - Outcomes: req.Outcomes, - Amount: domain.Currency(req.Amount), - TotalOdds: req.TotalOdds, - Status: req.Status, - FullName: req.FullName, - PhoneNumber: req.PhoneNumber, - BranchID: domain.ValidInt64{ - Value: branchID, - Valid: isShopBet, - }, - UserID: domain.ValidInt64{ - Value: userID, - Valid: !isShopBet, - }, - IsShopBet: req.IsShopBet, - }) - if err != nil { - h.logger.Error("Failed to create bet", "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet") - } - - // TODO: Reduce amount from the branch wallet (assuming walletSvc integration) - // This would typically be done here or in the bet service - - if !req.IsShopBet && req.PhoneNumber != "" { - if err := h.referralSvc.ProcessBetReferral(c.Context(), req.PhoneNumber, float64(req.Amount)); err != nil { - h.logger.Warn("Failed to process bet referral", "phone", req.PhoneNumber, "amount", req.Amount, "error", err) + // Get the branch from the branch ID + branch, err := h.branchSvc.GetBranchByCashier(c.Context(), user.ID) + if err != nil { + h.logger.Error("CreateBetReq failed, branch id invalid") + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } + + // Deduct a percentage of the amount + // TODO move to service layer + var deductedAmount = req.Amount / 10 + err = h.walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.ToCurrency(deductedAmount)) + + if err != nil { + h.logger.Error("CreateBetReq failed, unable to deduct from WalletID") + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{ + Amount: domain.ToCurrency(req.Amount), + TotalOdds: req.TotalOdds, + Status: req.Status, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + + BranchID: domain.ValidInt64{ + Value: branch.ID, + Valid: true, + }, + UserID: domain.ValidInt64{ + Value: userID, + Valid: false, + }, + IsShopBet: req.IsShopBet, + CashoutID: cashoutUUID.String(), + }) + } else { + // TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount + bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{ + Amount: domain.ToCurrency(req.Amount), + TotalOdds: req.TotalOdds, + Status: req.Status, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + + BranchID: domain.ValidInt64{ + Value: 0, + Valid: false, + }, + UserID: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + IsShopBet: req.IsShopBet, + CashoutID: cashoutUUID.String(), + }) } - res := convertBet(bet) - return response.WriteJSON(c, fiber.StatusOK, "Bet created successfully", res, nil) + if err != nil { + h.logger.Error("CreateBetReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) + } + + // + // TODO Validate Outcomes Here and make sure they didn't expire + // Validation for creating tickets + if len(req.Outcomes) > 30 { + return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) + } + var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes)) + for _, outcome := range req.Outcomes { + eventIDStr := strconv.FormatInt(outcome.EventID, 10) + marketIDStr := strconv.FormatInt(outcome.MarketID, 10) + oddIDStr := strconv.FormatInt(outcome.OddID, 10) + event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr) + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) + } + + // Checking to make sure the event hasn't already started + currentTime := time.Now() + if event.StartTime.Before(currentTime) { + return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) + } + + odds, err := h.oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) + + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) + } + type rawOddType struct { + ID string + Name string + Odds string + Header string + Handicap string + } + var selectedOdd rawOddType + var isOddFound bool = false + for _, raw := range odds.RawOdds { + var rawOdd rawOddType + rawBytes, err := json.Marshal(raw) + err = json.Unmarshal(rawBytes, &rawOdd) + if err != nil { + h.logger.Error("Failed to unmarshal raw odd:", err) + continue + } + if rawOdd.ID == oddIDStr { + selectedOdd = rawOdd + isOddFound = true + } + } + + if !isOddFound { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) + } + + parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) + + outcomes = append(outcomes, domain.CreateBetOutcome{ + BetID: bet.ID, + EventID: outcome.EventID, + OddID: outcome.OddID, + MarketID: outcome.MarketID, + HomeTeamName: event.HomeTeam, + AwayTeamName: event.AwayTeam, + MarketName: odds.MarketName, + Odd: float32(parsedOdd), + OddName: selectedOdd.Name, + OddHeader: selectedOdd.Header, + OddHandicap: selectedOdd.Handicap, + Expires: event.StartTime, + }) + } + + rows, err := h.betSvc.CreateBetOutcome(c.Context(), outcomes) + + if err != nil { + h.logger.Error("CreateBetReq failed to create outcomes", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + res := convertCreateBet(bet, rows) + + return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) + } // GetAllBet godoc @@ -132,19 +310,6 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /bet/{id} [get] func (h *Handler) GetBetByID(c *fiber.Ctx) error { - type BetRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []domain.Outcome `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` - Status domain.BetStatus `json:"status" example:"1"` - FullName string `json:"full_name" example:"John"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - BranchID int64 `json:"branch_id" example:"2"` - UserID int64 `json:"user_id" example:"2"` - IsShopBet bool `json:"is_shop_bet" example:"false"` - } - betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { @@ -159,7 +324,47 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error { } res := convertBet(bet) + return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) + +} + +// GetBetByCashoutID godoc +// @Summary Gets bet by cashout id +// @Description Gets a single bet by cashout id +// @Tags bet +// @Accept json +// @Produce json +// @Param id path string true "cashout ID" +// @Success 200 {object} BetRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /bet/cashout/{id} [get] +func GetBetByCashoutID(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + cashoutID := c.Params("id") + // id, err := strconv.ParseInt(cashoutID, 10, 64) + + // if err != nil { + // logger.Error("Invalid cashout ID", "cashoutID", cashoutID, "error", err) + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil) + // } + + bet, err := betSvc.GetBetByCashoutID(c.Context(), cashoutID) + if err != nil { + logger.Error("Failed to get bet by ID", "cashoutID", cashoutID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil) + } + + res := convertBet(bet) + + return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) + + } +} + +type UpdateCashOutReq struct { + CashedOut bool } // UpdateCashOut godoc @@ -189,7 +394,7 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error { var req UpdateCashOutReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse UpdateCashOut request", "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request body", err, nil) } if valErrs, ok := h.validator.Validate(c, req); !ok { @@ -232,18 +437,3 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil) } - -func convertBet(bet domain.Bet) BetRes { - return BetRes{ - ID: bet.ID, - Outcomes: bet.Outcomes, - Amount: bet.Amount.Float64(), - TotalOdds: bet.TotalOdds, - Status: bet.Status, - FullName: bet.FullName, - PhoneNumber: bet.PhoneNumber, - BranchID: bet.BranchID.Value, - UserID: bet.UserID.Value, - IsShopBet: bet.IsShopBet, - } -} diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go new file mode 100644 index 0000000..0741049 --- /dev/null +++ b/internal/web_server/handlers/branch_handler.go @@ -0,0 +1,664 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type CreateBranchReq struct { + Name string `json:"name" example:"4-kilo Branch"` + Location string `json:"location" example:"Addis Ababa"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` + Operations []int64 `json:"operations"` +} + +type CreateSupportedOperationReq struct { + Name string `json:"name" example:"SportsBook"` + Description string `json:"description" example:"Betting on sport events"` +} + +type SupportedOperationRes struct { + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"SportsBook"` + Description string `json:"description" example:"Betting on sport events"` +} + +type CreateBranchOperationReq struct { + BranchID int64 `json:"branch_id" example:"1"` + OperationID int64 `json:"operation_id" example:"1"` +} + +type BranchOperationRes struct { + Name string `json:"name" example:"SportsBook"` + Description string `json:"description" example:"Betting on sport events"` +} + +type BranchRes struct { + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"4-kilo Branch"` + Location string `json:"location" example:"Addis Ababa"` + WalletID int64 `json:"wallet_id" example:"1"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` +} + +type BranchDetailRes struct { + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"4-kilo Branch"` + Location string `json:"location" example:"Addis Ababa"` + WalletID int64 `json:"wallet_id" example:"1"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` + ManagerName string `json:"manager_name" example:"John Smith"` + ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"` +} + +func convertBranch(branch domain.Branch) BranchRes { + return BranchRes{ + ID: branch.ID, + Name: branch.Name, + Location: branch.Location, + WalletID: branch.WalletID, + BranchManagerID: branch.BranchManagerID, + CompanyID: branch.CompanyID, + IsSelfOwned: branch.IsSelfOwned, + } +} + +func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes { + return BranchDetailRes{ + ID: branch.ID, + Name: branch.Name, + Location: branch.Location, + WalletID: branch.WalletID, + BranchManagerID: branch.BranchManagerID, + CompanyID: branch.CompanyID, + IsSelfOwned: branch.IsSelfOwned, + ManagerName: branch.ManagerName, + ManagerPhoneNumber: branch.ManagerPhoneNumber, + } +} + +// CreateBranch godoc +// @Summary Create a branch +// @Description Creates a branch +// @Tags branch +// @Accept json +// @Produce json +// @Param createBranch body CreateBranchReq true "Creates branch" +// @Success 200 {object} BranchRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch [post] +func CreateBranch(logger *slog.Logger, branchSvc *branch.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // Check if user is either branch manager / super main + // role := string(c.Locals("role").(domain.Role)) + + // if role != string(domain.RoleAdmin) && role != string(domain.RoleSuperAdmin) && role != string(domain.RoleBranchManager) { + // logger.Error("Unauthorized access", "role", role) + // return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + // } + + var req CreateBranchReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateBranchReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + // Create Branch Wallet + newWallet, err := walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + IsTransferable: true, + UserID: req.BranchManagerID, + }) + + if err != nil { + logger.Error("Create Branch Wallet failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create branch wallet", err, nil) + } + + branch, err := branchSvc.CreateBranch(c.Context(), domain.CreateBranch{ + Name: req.Name, + Location: req.Location, + WalletID: newWallet.ID, + BranchManagerID: req.BranchManagerID, + CompanyID: req.CompanyID, + IsSelfOwned: req.IsSelfOwned, + }) + + if err != nil { + logger.Error("CreateBranchReq failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + for _, operation := range req.Operations { + err := branchSvc.CreateBranchOperation(c.Context(), domain.CreateBranchOperation{ + BranchID: branch.ID, + OperationID: operation, + }) + if err != nil { + logger.Error("Failed to create branch operations", "BranchID", branch.ID, "operation", operation, "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + } + + res := convertBranch(branch) + + return response.WriteJSON(c, fiber.StatusCreated, "Branch Created", res, nil) + + } + +} + +// CreateSupportedOperation godoc +// @Summary Create a supported operation +// @Description Creates a supported operation +// @Tags branch +// @Accept json +// @Produce json +// @Param createSupportedOperation body CreateSupportedOperationReq true "Creates supported operation" +// @Success 200 {object} SupportedOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /supportedOperation [post] +func CreateSupportedOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateSupportedOperationReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateSupportedOperationReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + operation, err := branchSvc.CreateSupportedOperation(c.Context(), domain.CreateSupportedOperation{ + Name: req.Name, + Description: req.Description, + }) + + if err != nil { + logger.Error("CreateSupportedOperationReq failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + res := SupportedOperationRes{ + Name: operation.Name, + Description: operation.Description, + } + + return response.WriteJSON(c, fiber.StatusOK, "Operation Created", res, nil) + + } +} + +// CreateBranchOperation godoc +// @Summary Create a operation +// @Description Creates a operation +// @Tags branch +// @Accept json +// @Produce json +// @Param createBranchOperation body CreateBranchOperationReq true "Creates operation" +// @Success 200 {object} BranchOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /operation [post] +func CreateBranchOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateBranchOperationReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateBranchOperationReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + err := branchSvc.CreateBranchOperation(c.Context(), domain.CreateBranchOperation{ + BranchID: req.BranchID, + OperationID: req.OperationID, + }) + + if err != nil { + logger.Error("CreateBranchOperationReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) + } + + 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 +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Success 200 {object} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [get] +func GetBranchByID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + if err != nil { + logger.Error("Invalid branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil) + } + + branch, err := branchSvc.GetBranchByID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get branch by ID", "branchID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch", err, nil) + } + + res := convertBranchDetail(branch) + + return response.WriteJSON(c, fiber.StatusOK, "Branch retrieved successfully", res, nil) + + } +} + +// GetBranchByManagerID godoc +// @Summary Gets branches by manager id +// @Description Gets a branches by manager id +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "User ID" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /manager/{id}/branch [get] +func GetBranchByManagerID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + // TODO: Restrict any who isn't branch manager or higher + userID := c.Params("id") + id, err := strconv.ParseInt(userID, 10, 64) + if err != nil { + logger.Error("Invalid user ID", "userID", userID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid user ID", err, nil) + } + + branches, err := branchSvc.GetBranchByManagerID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get branches", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) + } + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) + for _, branch := range branches { + result = append(result, convertBranchDetail(branch)) + } + return response.WriteJSON(c, fiber.StatusOK, "Branches for Branch Manager retrieved", result, nil) + } +} + +// GetBranchByCompanyID godoc +// @Summary Gets branches by company id +// @Description Gets branches by company id +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id}/branch [get] +func GetBranchByCompanyID(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + if err != nil { + logger.Error("Invalid company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) + } + + branches, err := branchSvc.GetBranchByCompanyID(c.Context(), id) + if err != nil { + logger.Error("Failed to get branches", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) + } + + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) + for _, branch := range branches { + result = append(result, convertBranchDetail(branch)) + } + return response.WriteJSON(c, fiber.StatusOK, "Branches for Company retrieved", result, nil) + } +} + +// GetAllBranches godoc +// @Summary Gets all branches +// @Description Gets all branches +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch [get] +func GetAllBranches(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // TODO: Limit the get all branches to only the companies for branch manager and cashiers + branches, err := branchSvc.GetAllBranches(c.Context()) + if err != nil { + logger.Error("Failed to get branches", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get branches", err, nil) + } + + var result []BranchDetailRes = make([]BranchDetailRes, 0, len(branches)) + for _, branch := range branches { + result = append(result, convertBranchDetail(branch)) + } + return response.WriteJSON(c, fiber.StatusOK, "Branches for Company retrieved", result, nil) + + } +} + +// SearchBranch godoc +// @Summary Search branches +// @Description Search branches by name or location +// @Tags branch +// @Accept json +// @Produce json +// @Param q query string true "Search query" +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /search/branch [get] +func SearchBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // Get search query from request + searchQuery := c.Query("q") + if searchQuery == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Search query is required", nil, nil) + } + + // Call the service to search for branches + branches, err := branchSvc.SearchBranchByName(c.Context(), searchQuery) + if err != nil { + logger.Error("Failed to search branches", "query", searchQuery, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to search branches", err, nil) + } + + // Convert branches to response format + var result []BranchDetailRes + for _, branch := range branches { + result = append(result, convertBranchDetail(branch)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branches retrieved successfully", result, nil) + } +} + +// GetAllSupportedOperations godoc +// @Summary Gets all supported operations +// @Description Gets all supported operations +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BranchDetailRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /supportedOperation [get] +func GetAllSupportedOperations(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + operations, err := branchSvc.GetAllSupportedOperations(c.Context()) + if err != nil { + logger.Error("Failed to get operations", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get operations", err, nil) + } + + var result []SupportedOperationRes = make([]SupportedOperationRes, 0, len(operations)) + for _, operation := range operations { + result = append(result, SupportedOperationRes{ + ID: operation.ID, + Name: operation.Name, + Description: operation.Description, + }) + } + return response.WriteJSON(c, fiber.StatusOK, "SupportedOperations for Company retrieved", result, nil) + + } +} + +// GetBranchOperations godoc +// @Summary Gets branch operations +// @Description Gets branch operations +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Success 200 {array} BranchOperationRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/operation [get] +func GetBranchOperations(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + if err != nil { + logger.Error("Invalid branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil) + } + + operations, err := branchSvc.GetBranchOperations(c.Context(), id) + + if err != nil { + logger.Error("Failed to get operation by ID", "branchID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve operation", err, nil) + } + + var result []BranchOperationRes = make([]BranchOperationRes, 0, len(operations)) + + for _, branch := range operations { + result = append(result, BranchOperationRes{ + Name: branch.OperationName, + Description: branch.OperationDescription, + }) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branch Operations retrieved successfully", result, nil) + } +} + +// GetBetByBranchID godoc +// @Summary Gets bets by its branch id +// @Description Gets bets by its branch id +// @Tags branch +// @Accept json +// @Produce json +// @Success 200 {array} BetRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/bets [get] +func GetBetByBranchID(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + if err != nil { + logger.Error("Invalid branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil) + } + + bets, err := betSvc.GetBetByBranchID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get bets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil) + } + + var res []BetRes = make([]BetRes, 0, len(bets)) + for _, bet := range bets { + res = append(res, convertBet(bet)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branch Bets Retrieved", res, nil) + } +} + +// UpdateBranch godoc +// @Summary Updates a branch +// @Description Updates a branch +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Param updateBranch body CreateBranchReq true "Update Branch" +// @Success 200 {object} BranchRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [put] +func UpdateBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + if err != nil { + logger.Error("Invalid branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil) + } + + var req CreateBranchReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateBranchReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + branch, err := branchSvc.UpdateBranch(c.Context(), id, domain.UpdateBranch{ + Name: req.Name, + Location: req.Location, + BranchManagerID: req.BranchManagerID, + CompanyID: req.CompanyID, + IsSelfOwned: req.IsSelfOwned, + }) + + if err != nil { + logger.Error("Failed to update branch", "branchID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update branch", err, nil) + } + + res := convertBranch(branch) + + return response.WriteJSON(c, fiber.StatusOK, "Branch Updated", res, nil) + + } +} + +// DeleteBranch godoc +// @Summary Delete the branch +// @Description Delete the branch +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID"" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id} [delete] +func DeleteBranch(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + branchID := c.Params("id") + id, err := strconv.ParseInt(branchID, 10, 64) + + if err != nil { + logger.Error("Invalid Branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Branch ID", err, nil) + } + + err = branchSvc.DeleteBranch(c.Context(), id) + + if err != nil { + logger.Error("Failed to delete by ID", "Branch ID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Branch", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branch removed successfully", nil, nil) + + } +} + +// DeleteBranchOperation godoc +// @Summary Delete the branch operation +// @Description Delete the branch operation +// @Tags branch +// @Accept json +// @Produce json +// @Param id path int true "Branch ID" +// @Param opID path int true "Branch Operation ID" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branch/{id}/operation/{opID} [delete] +func DeleteBranchOperation(logger *slog.Logger, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + branchID := c.Params("id") + opID := c.Params("opID") + + id, err := strconv.ParseInt(branchID, 10, 64) + + if err != nil { + logger.Error("Invalid Branch ID", "branchID", branchID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Branch ID", err, nil) + } + + operationID, err := strconv.ParseInt(opID, 10, 64) + + if err != nil { + logger.Error("Invalid Operation ID", "operationID", opID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Operation ID", err, nil) + } + + err = branchSvc.DeleteBranchOperation(c.Context(), id, operationID) + + if err != nil { + logger.Error("Failed to delete operation", "Branch ID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Operation", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Branch Operation removed successfully", nil, nil) + + } +} diff --git a/internal/web_server/handlers/cashier.go b/internal/web_server/handlers/cashier.go new file mode 100644 index 0000000..ffce3ae --- /dev/null +++ b/internal/web_server/handlers/cashier.go @@ -0,0 +1,208 @@ +package handlers + +import ( + "log/slog" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type CreateCashierReq 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"` + BranchID int64 `json:"branch_id" example:"1"` +} + +// CreateCashier godoc +// @Summary Create cashier +// @Description Create cashier +// @Tags cashier +// @Accept json +// @Produce json +// @Param cashier body CreateCashierReq true "Create cashier" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /cashiers [post] +func CreateCashier(logger *slog.Logger, userSvc *user.Service, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + var req CreateCashierReq + if err := c.BodyParser(&req); err != nil { + logger.Error("RegisterUser failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + userRequest := domain.CreateUserReq{ + FirstName: req.FirstName, + LastName: req.LastName, + Email: req.Email, + PhoneNumber: req.PhoneNumber, + Password: req.Password, + Role: string(domain.RoleCashier), + } + newUser, err := userSvc.CreateUser(c.Context(), userRequest) + if err != nil { + logger.Error("CreateCashier failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) + } + + err = branchSvc.CreateBranchCashier(c.Context(), req.BranchID, newUser.ID) + if err != nil { + logger.Error("CreateCashier failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil) + } + +} + +type GetCashierRes 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"` + SuspendedAt time.Time `json:"suspended_at"` + Suspended bool `json:"suspended"` +} + +// GetAllCashiers godoc +// @Summary Get all cashiers +// @Description Get all cashiers +// @Tags cashier +// @Accept json +// @Produce json +// @Param page query int false "Page number" +// @Param page_size query int false "Page size" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /cashiers [get] +func GetAllCashiers(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + // branchId := int64(12) //c.Locals("branch_id").(int64) + // filter := user.Filter{ + // Role: string(domain.RoleCashier), + // BranchId: user.ValidBranchId{ + // Value: branchId, + // Valid: true, + // }, + // Page: c.QueryInt("page", 1), + // PageSize: c.QueryInt("page_size", 10), + // } + // valErrs, ok := validator.Validate(c, filter) + // if !ok { + // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + // } + + cashiers, err := userSvc.GetAllCashiers(c.Context()) + if err != nil { + logger.Error("GetAllCashiers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) + } + + var result []GetCashierRes + + for _, cashier := range cashiers { + result = append(result, GetCashierRes{ + ID: cashier.ID, + FirstName: cashier.FirstName, + LastName: cashier.LastName, + Email: cashier.Email, + PhoneNumber: cashier.PhoneNumber, + Role: cashier.Role, + EmailVerified: cashier.EmailVerified, + PhoneVerified: cashier.PhoneVerified, + CreatedAt: cashier.CreatedAt, + UpdatedAt: cashier.UpdatedAt, + SuspendedAt: cashier.SuspendedAt, + Suspended: cashier.Suspended, + }) + } + + return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil) + } + +} + +type updateUserReq struct { + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Suspended bool `json:"suspended" example:"false"` +} + +// UpdateCashier godoc +// @Summary Update cashier +// @Description Update cashier +// @Tags cashier +// @Accept json +// @Produce json +// @Param cashier body updateUserReq true "Update cashier" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /cashiers/{id} [put] +func UpdateCashier(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req updateUserReq + if err := c.BodyParser(&req); err != nil { + logger.Error("UpdateCashier failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) + } + + valErrs, ok := validator.Validate(c, req) + + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + cashierIdStr := c.Params("id") + cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64) + if err != nil { + logger.Error("UpdateCashier failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil) + } + err = userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ + UserId: cashierId, + 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 { + logger.Error("UpdateCashier failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cashier", nil, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil) + } + +} diff --git a/internal/web_server/handlers/company_handler.go b/internal/web_server/handlers/company_handler.go new file mode 100644 index 0000000..0fb9449 --- /dev/null +++ b/internal/web_server/handlers/company_handler.go @@ -0,0 +1,229 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type CreateCompanyReq struct { + Name string `json:"name" example:"CompanyName"` + AdminID int64 `json:"admin_id" example:"1"` +} + +type CompanyRes struct { + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"CompanyName"` + AdminID int64 `json:"admin_id" example:"1"` + WalletID int64 `json:"wallet_id" example:"1"` +} + +func convertCompany(company domain.Company) CompanyRes { + return CompanyRes{ + ID: company.ID, + Name: company.Name, + AdminID: company.AdminID, + WalletID: company.WalletID, + } +} + +// CreateCompany godoc +// @Summary Create a company +// @Description Creates a company +// @Tags company +// @Accept json +// @Produce json +// @Param createCompany body CreateCompanyReq true "Creates company" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company [post] +func CreateCompany(logger *slog.Logger, companySvc *company.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateCompanyReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + // Create Branch Wallet + newWallet, err := walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + IsTransferable: true, + UserID: req.AdminID, + }) + + if err != nil { + logger.Error("Create Company Wallet failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create company wallet", err, nil) + } + + company, err := companySvc.CreateCompany(c.Context(), domain.CreateCompany{ + Name: req.Name, + AdminID: req.AdminID, + WalletID: newWallet.ID, + }) + + if err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusCreated, "Company Created", res, nil) + + } +} + +// GetAllCompanies godoc +// @Summary Gets all companies +// @Description Gets all companies +// @Tags company +// @Accept json +// @Produce json +// @Success 200 {array} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company [get] +func GetAllCompanies(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companies, err := companySvc.GetAllCompanies(c.Context()) + if err != nil { + logger.Error("Failed to get companies", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) + } + + var result []CompanyRes = make([]CompanyRes, 0, len(companies)) + + for _, company := range companies { + result = append(result, convertCompany(company)) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil) + } +} + +// GetCompanyByID godoc +// @Summary Gets company by id +// @Description Gets a single company by id +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [get] +func GetCompanyByID(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + if err != nil { + logger.Error("Invalid company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) + } + + company, err := companySvc.GetCompanyByID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get company by ID", "companyID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) + } +} + +// UpdateCompany godoc +// @Summary Updates a company +// @Description Updates a company +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID" +// @Param updateCompany body CreateCompanyReq true "Update Company" +// @Success 200 {object} CompanyRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [put] +func UpdateCompany(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + if err != nil { + logger.Error("Invalid company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid company ID", err, nil) + } + + var req CreateCompanyReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateCompanyReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + company, err := companySvc.UpdateCompany(c.Context(), id, domain.UpdateCompany{ + Name: req.Name, + AdminID: req.AdminID, + }) + + if err != nil { + logger.Error("Failed to update company", "companyID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", err, nil) + } + + res := convertCompany(company) + + return response.WriteJSON(c, fiber.StatusOK, "Company Updated", res, nil) + } +} + +// DeleteCompany godoc +// @Summary Delete the company +// @Description Delete the company +// @Tags company +// @Accept json +// @Produce json +// @Param id path int true "Company ID"" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /company/{id} [delete] +func DeleteCompany(logger *slog.Logger, companySvc *company.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + companyID := c.Params("id") + id, err := strconv.ParseInt(companyID, 10, 64) + + if err != nil { + logger.Error("Invalid Company ID", "companyID", companyID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Company ID", err, nil) + } + + err = companySvc.DeleteCompany(c.Context(), id) + if err != nil { + logger.Error("Failed to delete by ID", "Company ID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to Delete Company", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Company removed successfully", nil, nil) + } +} diff --git a/internal/web_server/handlers/manager.go b/internal/web_server/handlers/manager.go new file mode 100644 index 0000000..577420d --- /dev/null +++ b/internal/web_server/handlers/manager.go @@ -0,0 +1,156 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type CreateManagerReq 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"` +} + +// CreateManagers godoc +// @Summary Create Managers +// @Description Create Managers +// @Tags manager +// @Accept json +// @Produce json +// @Param manger body CreateManagerReq true "Create manager" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /managers [post] +func CreateManager(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateManagerReq + if err := c.BodyParser(&req); err != nil { + logger.Error("RegisterUser failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + user := domain.CreateUserReq{ + FirstName: req.FirstName, + LastName: req.LastName, + Email: req.Email, + PhoneNumber: req.PhoneNumber, + Password: req.Password, + Role: string(domain.RoleBranchManager), + } + _, err := userSvc.CreateUser(c.Context(), user) + if err != nil { + logger.Error("CreateManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create Managers", nil, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Managers created successfully", nil, nil) + } + +} + +// GetAllManagers godoc +// @Summary Get all Managers +// @Description Get all Managers +// @Tags manager +// @Accept json +// @Produce json +// @Param page query int false "Page number" +// @Param page_size query int false "Page size" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /managers [get] +func GetAllManagers(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + filter := user.Filter{ + Role: string(domain.RoleBranchManager), + BranchId: user.ValidBranchId{ + Value: int64(c.QueryInt("branch_id")), + Valid: true, + }, + Page: c.QueryInt("page", 1), + PageSize: c.QueryInt("page_size", 10), + } + valErrs, ok := validator.Validate(c, filter) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + Managers, err := userSvc.GetAllUsers(c.Context(), filter) + if err != nil { + logger.Error("GetAllManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Managers retrieved successfully", Managers, nil) + } + +} + +// UpdateManagers godoc +// @Summary Update Managers +// @Description Update Managers +// @Tags Managers +// @Accept json +// @Produce json +// @Param Managers body updateUserReq true "Update Managers" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 401 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /managers/{id} [put] +func UPdateManagers(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req updateUserReq + if err := c.BodyParser(&req); err != nil { + logger.Error("UpdateManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil) + } + + valErrs, ok := validator.Validate(c, req) + + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + ManagersIdStr := c.Params("id") + ManagersId, err := strconv.ParseInt(ManagersIdStr, 10, 64) + if err != nil { + logger.Error("UpdateManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil) + } + err = userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ + UserId: ManagersId, + 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 { + logger.Error("UpdateManagers failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Managers", nil, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil) + } + +} diff --git a/internal/web_server/handlers/prematch.go b/internal/web_server/handlers/prematch.go new file mode 100644 index 0000000..10df2a5 --- /dev/null +++ b/internal/web_server/handlers/prematch.go @@ -0,0 +1,195 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + "github.com/gofiber/fiber/v2" +) + +// GetPrematchOdds godoc +// @Summary Retrieve prematch odds for an event +// @Description Retrieve prematch odds for a specific event by event ID +// @Tags prematch +// @Accept json +// @Produce json +// @Param event_id path string true "Event ID" +// @Success 200 {array} domain.Odd +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds/{event_id} [get] +func GetPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + eventID := c.Params("event_id") + if eventID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing event_id", nil, nil) + } + + odds, err := prematchSvc.GetPrematchOdds(c.Context(), eventID) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve odds", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil) + } +} + +// GetALLPrematchOdds +// @Summary Retrieve all prematch odds +// @Description Retrieve all prematch odds from the database +// @Tags prematch +// @Accept json +// @Produce json +// @Success 200 {array} domain.Odd +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds [get] +func GetALLPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + odds, err := prematchSvc.GetALLPrematchOdds(c.Context()) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all prematch odds", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "All prematch odds retrieved successfully", odds, nil) + } +} + +// GetRawOddsByMarketID +// @Summary Retrieve raw odds by Market ID +// @Description Retrieve raw odds records using a Market ID +// @Tags prematch +// @Accept json +// @Produce json +// @Param upcoming_id path string true "Upcoming ID" +// @Param market_id path string true "Market ID" +// @Success 200 {array} domain.RawOddsByMarketID +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds/upcoming/{upcoming_id}/market/{market_id} [get] +func GetRawOddsByMarketID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + marketID := c.Params("market_id") + upcomingID := c.Params("upcoming_id") + if marketID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing market_id", nil, nil) + } + + if upcomingID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil) + } + + rawOdds, err := prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID) + if err != nil { + logger.Error("failed to fetch raw odds", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Raw odds retrieved successfully", rawOdds, nil) + } +} + +// @Summary Retrieve all upcoming events +// @Description Retrieve all upcoming events from the database +// @Tags prematch +// @Accept json +// @Produce json +// @Param page query int false "Page number" +// @Param page_size query int false "Page size" +// @Param league_id query string false "League ID Filter" +// @Param sport_id query string false "Sport ID Filter" +// @Success 200 {array} domain.UpcomingEvent +// @Failure 500 {object} response.APIResponse +// @Router /prematch/events [get] +func GetAllUpcomingEvents(logger *slog.Logger, eventSvc event.Service) fiber.Handler { + return func(c *fiber.Ctx) error { + page := c.QueryInt("page", 1) + pageSize := c.QueryInt("page_size", 10) + leagueIDQuery := c.Query("league_id") + sportIDQuery := c.Query("sport_id") + + leagueID := domain.ValidString{ + Value: leagueIDQuery, + Valid: leagueIDQuery != "", + } + sportID := domain.ValidString{ + Value: sportIDQuery, + Valid: sportIDQuery != "", + } + + events, total, err := eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page)-1, leagueID, sportID) + + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil) + } + + return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil, page, int(total)) + } +} + +// @Summary Retrieve an upcoming by ID +// @Description Retrieve an upcoming event by ID +// @Tags prematch +// @Accept json +// @Produce json +// @Param id path string true "ID" +// @Success 200 {object} domain.UpcomingEvent +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /prematch/events/{id} [get] +func GetUpcomingEventByID(logger *slog.Logger, eventSvc event.Service) fiber.Handler { + return func(c *fiber.Ctx) error { + id := c.Params("id") + if id == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing id", nil, nil) + } + + event, err := eventSvc.GetUpcomingEventByID(c.Context(), id) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve upcoming event", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", event, nil) + } +} + +// @Summary Retrieve prematch odds by upcoming ID (FI) +// @Description Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination +// @Tags prematch +// @Accept json +// @Produce json +// @Param upcoming_id path string true "Upcoming Event ID (FI)" +// @Param limit query int false "Number of results to return (default: 10)" +// @Param offset query int false "Number of results to skip (default: 0)" +// @Success 200 {array} domain.Odd +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds/upcoming/{upcoming_id} [get] +func GetPrematchOddsByUpcomingID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + upcomingID := c.Params("upcoming_id") + if upcomingID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil) + } + + limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 + if err != nil || limit <= 0 { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid limit value", nil, nil) + } + + offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 + if err != nil || offset < 0 { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil) + } + + odds, err := prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID, int32(limit), int32(offset)) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil) + } +} diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index 310c149..6470586 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -2,12 +2,42 @@ package handlers import ( "strconv" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" ) +type CreateTicketOutcomeReq struct { + // TicketID int64 `json:"ticket_id" example:"1"` + EventID int64 `json:"event_id" example:"1"` + OddID int64 `json:"odd_id" example:"1"` + MarketID int64 `json:"market_id" example:"1"` + // HomeTeamName string `json:"home_team_name" example:"Manchester"` + // AwayTeamName string `json:"away_team_name" example:"Liverpool"` + // MarketName string `json:"market_name" example:"Fulltime Result"` + // Odd float32 `json:"odd" example:"1.5"` + // OddName string `json:"odd_name" example:"1"` + // Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` +} + +type CreateTicketReq struct { + Outcomes []CreateTicketOutcomeReq `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` +} +type CreateTicketRes struct { + FastCode int64 `json:"fast_code" example:"1234"` + CreatedNumber int64 `json:"created_number" example:"3"` +} +type TicketRes struct { + ID int64 `json:"id" example:"1"` + Outcomes []domain.TicketOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` +} + // CreateTicket godoc // @Summary Create a temporary ticket // @Description Creates a temporary ticket @@ -40,22 +70,106 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - // TODO: Validate Outcomes Here and make sure they didn't expire + // TODO Validate Outcomes Here and make sure they didn't expire + // Validation for creating tickets + if len(req.Outcomes) > 30 { + return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) + } + var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) + for _, outcome := range req.Outcomes { + eventIDStr := strconv.FormatInt(outcome.EventID, 10) + marketIDStr := strconv.FormatInt(outcome.MarketID, 10) + oddIDStr := strconv.FormatInt(outcome.OddID, 10) + event, err := eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr) + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil) + } - ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ - Outcomes: req.Outcomes, - Amount: domain.Currency(req.Amount), - TotalOdds: req.TotalOdds, - }) - if err != nil { - h.logger.Error("Failed to create ticket", "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to create ticket") - } + // Checking to make sure the event hasn't already started + currentTime := time.Now() + if event.StartTime.Before(currentTime) { + return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) + } - res := CreateTicketRes{ - FastCode: ticket.ID, + odds, err := oddSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) + + if err != nil { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil) + } + type rawOddType struct { + ID string + Name string + Odds string + Header string + Handicap string + } + var selectedOdd rawOddType + var isOddFound bool = false + for _, raw := range odds.RawOdds { + var rawOdd rawOddType + rawBytes, err := json.Marshal(raw) + err = json.Unmarshal(rawBytes, &rawOdd) + if err != nil { + fmt.Println("Failed to unmarshal raw odd:", err) + continue + } + if rawOdd.ID == oddIDStr { + selectedOdd = rawOdd + isOddFound = true + } + } + + if !isOddFound { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil) + } + + parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) + + outcomes = append(outcomes, domain.CreateTicketOutcome{ + EventID: outcome.EventID, + OddID: outcome.OddID, + MarketID: outcome.MarketID, + HomeTeamName: event.HomeTeam, + AwayTeamName: event.AwayTeam, + MarketName: odds.MarketName, + Odd: float32(parsedOdd), + OddName: selectedOdd.Name, + OddHeader: selectedOdd.Header, + OddHandicap: selectedOdd.Handicap, + Expires: event.StartTime, + }) + } + + ticket, err := ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ + Amount: domain.ToCurrency(req.Amount), + TotalOdds: req.TotalOdds, + }) + if err != nil { + logger.Error("CreateTicketReq failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + + // Add the ticket id now that it has fetched from the database + for index := range outcomes { + outcomes[index].TicketID = ticket.ID + } + + rows, err := ticketSvc.CreateTicketOutcome(c.Context(), outcomes) + + if err != nil { + logger.Error("CreateTicketReq failed to create outcomes", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + res := CreateTicketRes{ + FastCode: ticket.ID, + CreatedNumber: rows, + } + return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil) } - return response.WriteJSON(c, fiber.StatusOK, "Ticket created successfully", res, nil) } // GetTicketByID godoc diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index 9e3519e..b8b2692 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -9,12 +9,12 @@ import ( ) type TransactionRes struct { - ID int64 `json:"id" example:"1"` - Amount float32 `json:"amount" example:"100.0"` - BranchID int64 `json:"branch_id" example:"1"` - CashierID int64 `json:"cashier_id" example:"1"` - BetID int64 `json:"bet_id" example:"1"` - + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + BranchID int64 `json:"branch_id" example:"1"` + CashierID int64 `json:"cashier_id" example:"1"` + BetID int64 `json:"bet_id" example:"1"` + Type int64 `json:"type" example:"1"` PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` FullName string `json:"full_name" example:"John Smith"` PhoneNumber string `json:"phone_number" example:"0911111111"` @@ -27,19 +27,18 @@ type TransactionRes struct { } type CreateTransactionReq struct { - Amount float32 `json:"amount" example:"100.0"` - BranchID int64 `json:"branch_id" example:"1"` - CashierID int64 `json:"cashier_id" example:"1"` - BetID int64 `json:"bet_id" example:"1"` - PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` - FullName string `json:"full_name" example:"John Smith"` - PhoneNumber string `json:"phone_number" example:"0911111111"` - // Payment Details for bank - BankCode string `json:"bank_code"` - BeneficiaryName string `json:"beneficiary_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - ReferenceNumber string `json:"reference_number"` + CashoutID string `json:"cashout_id" example:"191212"` + Amount float32 `json:"amount" example:"100.0"` + BetID int64 `json:"bet_id" example:"1"` + Type int64 `json:"type" example:"1"` + PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` } func convertTransaction(transaction domain.Transaction) TransactionRes { @@ -49,6 +48,7 @@ func convertTransaction(transaction domain.Transaction) TransactionRes { BranchID: transaction.BranchID, CashierID: transaction.CashierID, BetID: transaction.BetID, + Type: int64(transaction.Type), PaymentOption: transaction.PaymentOption, FullName: transaction.FullName, PhoneNumber: transaction.PhoneNumber, @@ -72,57 +72,75 @@ func convertTransaction(transaction domain.Transaction) TransactionRes { // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction [post] -// Update transaction handler to include deposit bonus func (h *Handler) CreateTransaction(c *fiber.Ctx) error { - type CreateTransactionReq struct { - Amount float32 `json:"amount" validate:"required" example:"100.0"` - BranchID int64 `json:"branch_id" example:"1"` - CashierID int64 `json:"cashier_id" example:"1"` - BetID int64 `json:"bet_id" example:"1"` - PaymentOption domain.PaymentOption `json:"payment_option" validate:"required" example:"1"` - FullName string `json:"full_name" example:"John Smith"` - PhoneNumber string `json:"phone_number" validate:"required" example:"0911111111"` - BankCode string `json:"bank_code"` - BeneficiaryName string `json:"beneficiary_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - ReferenceNumber string `json:"reference_number"` - } + userID := c.Locals("user_id").(int64) + user, err := userSvc.GetUserByID(c.Context(), userID) - var req CreateTransactionReq - if err := c.BodyParser(&req); err != nil { - h.logger.Error("CreateTransaction failed to parse request", "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } + if user.Role == domain.RoleCustomer { + logger.Error("CreateTransactionReq failed") + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "error": "unauthorized access", + }) + } - if valErrs, ok := h.validator.Validate(c, req); !ok { - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) - } + // TODO: Add validation to make sure that the bet hasn't already been cashed out by someone else + var branchID int64 + if user.Role == domain.RoleAdmin || user.Role == domain.RoleBranchManager || user.Role == domain.RoleSuperAdmin { + branch, err := branchSvc.GetBranchByID(c.Context(), 1) + if err != nil { + logger.Error("CreateTransactionReq no branches") + return response.WriteJSON(c, fiber.StatusBadRequest, "This user type doesn't have branches", err, nil) + } - isDeposit := req.PaymentOption == domain.BANK + branchID = branch.ID - transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ - Amount: domain.Currency(req.Amount), - BranchID: req.BranchID, - CashierID: req.CashierID, - BetID: req.BetID, - PaymentOption: req.PaymentOption, - FullName: req.FullName, - PhoneNumber: req.PhoneNumber, - BankCode: req.BankCode, - BeneficiaryName: req.BeneficiaryName, - AccountName: req.AccountName, - AccountNumber: req.AccountNumber, - ReferenceNumber: req.ReferenceNumber, - }) - if err != nil { - h.logger.Error("CreateTransaction failed", "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transaction") - } + } else { + branch, err := branchSvc.GetBranchByCashier(c.Context(), user.ID) + if err != nil { + logger.Error("CreateTransactionReq failed, branch id invalid") + return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil) + } + branchID = branch.ID + } - if isDeposit { - if err := h.referralSvc.ProcessDepositBonus(c.Context(), req.PhoneNumber, float64(req.Amount)); err != nil { - h.logger.Warn("Failed to process deposit bonus", "phone", req.PhoneNumber, "amount", req.Amount, "error", err) + var req CreateTransactionReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("CreateTransaction failed to parse request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + logger.Error("CreateTransactionReq failed v", "error", valErrs) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ + BranchID: branchID, + CashierID: userID, + Amount: domain.ToCurrency(req.Amount), + BetID: req.BetID, + Type: domain.TransactionType(req.Type), + PaymentOption: domain.PaymentOption(req.PaymentOption), + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + BankCode: req.BankCode, + BeneficiaryName: req.BeneficiaryName, + AccountName: req.AccountName, + AccountNumber: req.AccountNumber, + ReferenceNumber: req.ReferenceNumber, + }) + + if err != nil { + logger.Error("CreateTransactionReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) + } + + err = betSvc.UpdateCashOut(c.Context(), req.BetID, true) + + if err != nil { + logger.Error("CreateTransactionReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } } @@ -141,18 +159,47 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { // @Failure 500 {object} response.APIResponse // @Router /transaction [get] func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { - transactions, err := h.transactionSvc.GetAllTransactions(c.Context()) - if err != nil { - h.logger.Error("Failed to get transactions", "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transactions") - } + // Get user_id from middleware + userID := c.Locals("user_id").(int64) + + // Fetch user details + user, err := userSvc.GetUserByID(c.Context(), userID) + if err != nil { + logger.Error("Failed to fetch user details", "user_id", userID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve user details", err, nil) + } + + var transactions []domain.Transaction + + // Check user role and fetch transactions accordingly + switch user.Role { + case domain.RoleSuperAdmin: + // Admin can fetch all transactions + transactions, err = transactionSvc.GetAllTransactions(c.Context()) + case domain.RoleAdmin: + // Admin can fetch all transactions + transactions, err = transactionSvc.GetAllTransactions(c.Context()) + case domain.RoleBranchManager, domain.RoleCashier: + // Branch Manager or Cashier can fetch transactions for their branch + // transactions, err = transactionSvc.GetTransactionByBranch(c.Context(), user.BranchID) + transactions, err = transactionSvc.GetAllTransactions(c.Context()) + default: + // Unauthorized role + return response.WriteJSON(c, fiber.StatusForbidden, "Unauthorized", nil, nil) + } + + if err != nil { + logger.Error("Failed to get transactions", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) + } res := make([]TransactionRes, len(transactions)) for i, transaction := range transactions { res[i] = convertTransaction(transaction) } - return response.WriteJSON(c, fiber.StatusOK, "All transactions retrieved", res, nil) + return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) + } } // GetTransactionByID godoc @@ -208,11 +255,11 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } - var req UpdateTransactionVerifiedReq - if err := c.BodyParser(&req); err != nil { - h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } + var req UpdateTransactionVerifiedReq + if err := c.BodyParser(&req); err != nil { + logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go new file mode 100644 index 0000000..df85550 --- /dev/null +++ b/internal/web_server/handlers/transfer_handler.go @@ -0,0 +1,245 @@ +package handlers + +import ( + "log/slog" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type TransferWalletRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + Verified bool `json:"verified" example:"true"` + Type string `json:"type" example:"transfer"` + PaymentMethod string `json:"payment_method" example:"bank"` + ReceiverWalletID int64 `json:"receiver_wallet_id" example:"1"` + SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` + CashierID *int64 `json:"cashier_id" example:"789"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` + UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:30:00Z"` +} +type RefillRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + Verified bool `json:"verified" example:"true"` + Type string `json:"type" example:"transfer"` + PaymentMethod string `json:"payment_method" example:"bank"` + ReceiverWalletID int64 `json:"receiver_wallet_id" example:"1"` + SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` + CashierID *int64 `json:"cashier_id" example:"789"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` + UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:30:00Z"` +} + +func convertTransfer(transfer domain.Transfer) TransferWalletRes { + var senderWalletID *int64 + if transfer.SenderWalletID.Valid { + senderWalletID = &transfer.SenderWalletID.Value + } + + var cashierID *int64 + if transfer.CashierID.Valid { + cashierID = &transfer.CashierID.Value + } + + return TransferWalletRes{ + ID: transfer.ID, + Amount: transfer.Amount.Float64(), + Verified: transfer.Verified, + Type: string(transfer.Type), + PaymentMethod: string(transfer.PaymentMethod), + ReceiverWalletID: transfer.ReceiverWalletID, + SenderWalletID: senderWalletID, + CashierID: cashierID, + CreatedAt: transfer.CreatedAt, + UpdatedAt: transfer.UpdatedAt, + } +} + +type CreateTransferReq struct { + Amount float32 `json:"amount" example:"100.0"` + PaymentMethod string `json:"payment_method" example:"cash"` +} + +type CreateRefillReq struct { + Amount float32 `json:"amount" example:"100.0"` +} + +// GetTransfersByWallet godoc +// @Summary Get transfer by wallet +// @Description Get transfer by wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param transferToWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/wallet/{id} [get] +func GetTransfersByWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + walletID := c.Params("id") + + id, err := strconv.ParseInt(walletID, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + + transfers, err := walletSvc.GetTransfersByWallet(c.Context(), int64(id)) + if err != nil { + logger.Error("Failed to get transfers by wallet", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transfers", err, nil) + } + + var transferResponses []TransferWalletRes + for _, transfer := range transfers { + transferResponses = append(transferResponses, convertTransfer(transfer)) + } + + return response.WriteJSON(c, fiber.StatusOK, "Transfers retrieved successfully", transferResponses, nil) + } +} + +// TransferToWallet godoc +// @Summary Create a transfer to wallet +// @Description Create a transfer to wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param transferToWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/wallet/:id [post] +func TransferToWallet(logger *slog.Logger, walletSvc *wallet.Service, branchSvc *branch.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + receiverIDString := c.Params("id") + + receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + // Get sender ID from the cashier + userID := c.Locals("user_id").(int64) + role := string(c.Locals("role").(domain.Role)) + + var senderID int64 + + if role == string(domain.RoleCustomer) { + logger.Error("Unauthorized access", "userID", userID, "role", role) + return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + } else if role == string(domain.RoleBranchManager) || role == string(domain.RoleAdmin) || role == string(domain.RoleSuperAdmin) { + // TODO Add a way for admins to reference branch wallet + senderID = 0 + logger.Error("Will", "userID", userID, "role", role) + return response.WriteJSON(c, fiber.StatusBadRequest, "Unauthorized access", nil, nil) + } else { + cashierBranch, err := branchSvc.GetBranchByCashier(c.Context(), userID) + if err != nil { + logger.Error("Failed to get branch", "user ID", userID, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil) + } + senderID = cashierBranch.WalletID + } + + var req CreateTransferReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateTransferReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + transfer, err := walletSvc.TransferToWallet(c.Context(), senderID, receiverID, domain.ToCurrency(req.Amount), domain.PaymentMethod(req.PaymentMethod), domain.ValidInt64{Value: userID, Valid: true}) + + if !ok { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil) + } + + res := convertTransfer(transfer) + + return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) + + } +} + +// RefillWallet godoc +// @Summary Refill wallet +// @Description Super Admin route to refill a wallet +// @Tags transfer +// @Accept json +// @Produce json +// @Param refillWallet body CreateTransferReq true "Create Transfer" +// @Success 200 {object} TransferWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transfer/refill/:id [post] +func RefillWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + receiverIDString := c.Params("id") + + receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", receiverID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + // Get sender ID from the cashier + userID := c.Locals("user_id").(int64) + role := string(c.Locals("role").(domain.Role)) + + if role != string(domain.RoleSuperAdmin) { + logger.Error("Unauthorized access", "userID", userID, "role", role) + return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + } + + var req CreateRefillReq + + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateRefillReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + transfer, err := walletSvc.RefillWallet(c.Context(), domain.CreateTransfer{ + Amount: domain.ToCurrency(req.Amount), + PaymentMethod: domain.TRANSFER_BANK, + ReceiverWalletID: receiverID, + CashierID: domain.ValidInt64{ + Value: userID, + Valid: true, + }, + Type: domain.TransferType("deposit"), + }) + + if !ok { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Creating Transfer Failed", err, nil) + } + + res := convertTransfer(transfer) + + return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) + + } +} diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 8565d65..2ecd0fb 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -356,3 +356,61 @@ func getMedium(email, phoneNumber string) (domain.OtpMedium, error) { } return "", errors.New("both email and phone number are empty") } + +type SearchUserByNameOrPhoneReq struct { + SearchString string +} + +// SearchUserByNameOrPhone godoc +// @Summary Search for user using name or phone +// @Description Search for user using name or phone +// @Tags user +// @Accept json +// @Produce json +// @Param searchUserByNameOrPhone body SearchUserByNameOrPhoneReq true "Search for using his name or phone" +// @Success 200 {object} UserProfileRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /user/search [post] +func SearchUserByNameOrPhone(logger *slog.Logger, userSvc *user.Service, + validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req SearchUserByNameOrPhoneReq + if err := c.BodyParser(&req); err != nil { + logger.Error("SearchUserByNameOrPhone failed", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request", + }) + } + valErrs, ok := validator.Validate(c, req) + if !ok { + response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + return nil + } + users, err := userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString) + if err != nil { + logger.Error("SearchUserByNameOrPhone failed", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Internal server error", + }) + } + var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) + for _, user := range users { + res = append(res, UserProfileRes{ + 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, + }) + } + return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil) + } +} diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index 4a88cf1..f40d813 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -9,6 +9,73 @@ import ( "github.com/gofiber/fiber/v2" ) +type WalletRes struct { + ID int64 `json:"id" example:"1"` + Balance float32 `json:"amount" example:"100.0"` + IsWithdraw bool `json:"is_withdraw" example:"true"` + IsBettable bool `json:"is_bettable" example:"true"` + IsTransferable bool `json:"is_transferable" example:"true"` + IsActive bool `json:"is_active" example:"true"` + UserID int64 `json:"user_id" example:"1"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func convertWallet(wallet domain.Wallet) WalletRes { + return WalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsTransferable: wallet.IsTransferable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + } +} + +type CustomerWalletRes struct { + ID int64 `json:"id" example:"1"` + RegularID int64 `json:"regular_id" example:"1"` + RegularBalance float32 `json:"regular_balance" example:"100.0"` + StaticID int64 `json:"static_id" example:"1"` + StaticBalance float32 `json:"static_balance" example:"100.0"` + CustomerID int64 `json:"customer_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + RegularUpdatedAt time.Time `json:"regular_updated_at"` + StaticUpdatedAt time.Time `json:"static_updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes { + return CustomerWalletRes{ + ID: wallet.ID, + RegularID: wallet.RegularID, + RegularBalance: wallet.RegularBalance.Float64(), + StaticID: wallet.StaticID, + StaticBalance: wallet.StaticBalance.Float64(), + CustomerID: wallet.CustomerID, + CompanyID: wallet.CompanyID, + RegularUpdatedAt: wallet.RegularUpdatedAt, + StaticUpdatedAt: wallet.StaticUpdatedAt, + CreatedAt: wallet.CreatedAt, + } +} + +type BranchWalletRes struct { + ID int64 `json:"id" example:"1"` + Balance float32 `json:"balance" example:"100.0"` + IsActive bool `json:"is_active" example:"true"` + Name string `json:"name" example:"true"` + Location string `json:"location" example:"somewhere"` + BranchManagerID int64 `json:"branch_manager_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + IsSelfOwned bool `json:"is_self_owned" example:"false"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + // GetWalletByID godoc // @Summary Get wallet by ID // @Description Retrieve wallet details by wallet ID @@ -45,16 +112,7 @@ func (h *Handler) GetWalletByID(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet") } - res := WalletRes{ - ID: wallet.ID, - Balance: wallet.Balance.Float64(), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt, - CreatedAt: wallet.CreatedAt, - } + res := convertWallet(wallet) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) } @@ -69,37 +127,66 @@ func (h *Handler) GetWalletByID(c *fiber.Ctx) error { // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /wallet [get] -func (h *Handler) GetAllWallets(c *fiber.Ctx) error { - type WalletRes struct { - ID int64 `json:"id" example:"1"` - Balance float32 `json:"amount" example:"100.0"` - IsWithdraw bool `json:"is_withdraw" example:"true"` - IsBettable bool `json:"is_bettable" example:"true"` - IsActive bool `json:"is_active" example:"true"` - UserID int64 `json:"user_id" example:"1"` - UpdatedAt time.Time `json:"updated_at"` - CreatedAt time.Time `json:"created_at"` - } +func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { - wallets, err := h.walletSvc.GetAllWallets(c.Context()) - if err != nil { - h.logger.Error("Failed to get wallets", "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallets") - } + wallets, err := walletSvc.GetAllWallets(c.Context()) - res := make([]WalletRes, len(wallets)) - for i, wallet := range wallets { - res[i] = WalletRes{ - ID: wallet.ID, - Balance: wallet.Balance.Float64(), - IsWithdraw: wallet.IsWithdraw, - IsBettable: wallet.IsBettable, - IsActive: wallet.IsActive, - UserID: wallet.UserID, - UpdatedAt: wallet.UpdatedAt, - CreatedAt: wallet.CreatedAt, + if err != nil { + logger.Error("Failed to get wallets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) } + + var res []WalletRes = make([]WalletRes, 0, len(wallets)) + + for _, wallet := range wallets { + res = append(res, convertWallet(wallet)) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) } +} + +// GetAllBranchWallets godoc +// @Summary Get all branch wallets +// @Description Retrieve all branch wallets +// @Tags wallet +// @Accept json +// @Produce json +// @Success 200 {array} WalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /branchWallet [get] +func GetAllBranchWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + wallets, err := walletSvc.GetAllBranchWallets(c.Context()) + + if err != nil { + logger.Error("Failed to get wallets", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallets", err, nil) + } + + var res []BranchWalletRes = make([]BranchWalletRes, 0, len(wallets)) + + for _, wallet := range wallets { + res = append(res, BranchWalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsActive: wallet.IsActive, + Name: wallet.Name, + Location: wallet.Location, + BranchManagerID: wallet.BranchManagerID, + CompanyID: wallet.CompanyID, + IsSelfOwned: wallet.IsSelfOwned, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + }) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) + } +} return response.WriteJSON(c, fiber.StatusOK, "All wallets retrieved successfully", res, nil) } diff --git a/internal/web_server/jwt/jwt.go b/internal/web_server/jwt/jwt.go index 35f8a94..22b76d6 100644 --- a/internal/web_server/jwt/jwt.go +++ b/internal/web_server/jwt/jwt.go @@ -17,8 +17,9 @@ var ( type UserClaim struct { jwt.RegisteredClaims - UserId int64 - Role domain.Role + UserId int64 + Role domain.Role + BranchId int64 } type PopOKClaim struct { @@ -36,7 +37,7 @@ type JwtConfig struct { JwtAccessExpiry int } -func CreateJwt(userId int64, Role domain.Role, key string, expiry int) (string, error) { +func CreateJwt(userId int64, Role domain.Role, BranchId int64, key string, expiry int) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: "github.com/lafetz/snippitstash", @@ -45,8 +46,9 @@ func CreateJwt(userId int64, Role domain.Role, key string, expiry int) (string, NotBefore: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)), }, - UserId: userId, - Role: Role, + UserId: userId, + Role: Role, + BranchId: BranchId, }) jwtToken, err := token.SignedString([]byte(key)) return jwtToken, err diff --git a/internal/web_server/middleware.go b/internal/web_server/middleware.go index 4f337fb..057295b 100644 --- a/internal/web_server/middleware.go +++ b/internal/web_server/middleware.go @@ -2,8 +2,10 @@ package httpserver import ( "errors" + "fmt" "strings" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" "github.com/gofiber/fiber/v2" ) @@ -12,10 +14,12 @@ func (a *App) authMiddleware(c *fiber.Ctx) error { authHeader := c.Get("Authorization") if authHeader == "" { + fmt.Println("Auth Header Missing") return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing") } if !strings.HasPrefix(authHeader, "Bearer ") { + fmt.Println("Invalid authorization header format") return fiber.NewError(fiber.StatusUnauthorized, "Invalid authorization header format") } @@ -24,8 +28,10 @@ func (a *App) authMiddleware(c *fiber.Ctx) error { claim, err := jwtutil.ParseJwt(accessToken, a.JwtConfig.JwtAccessKey) if err != nil { if errors.Is(err, jwtutil.ErrExpiredToken) { + fmt.Println("Token Expired") return fiber.NewError(fiber.StatusUnauthorized, "Access token expired") } + fmt.Println("Invalid Token") return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") } @@ -38,6 +44,16 @@ func (a *App) authMiddleware(c *fiber.Ctx) error { } c.Locals("user_id", claim.UserId) c.Locals("role", claim.Role) + c.Locals("branch_id", claim.BranchId) c.Locals("refresh_token", refreshToken) + + return c.Next() +} + +func (a *App) SuperAdminOnly(c *fiber.Ctx) error { + userRole := c.Locals("role").(domain.Role) + if userRole != domain.RoleSuperAdmin { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token") + } return c.Next() } diff --git a/internal/web_server/response/res.go b/internal/web_server/response/res.go index 593758d..496a14f 100644 --- a/internal/web_server/response/res.go +++ b/internal/web_server/response/res.go @@ -18,12 +18,15 @@ type APIResponse struct { Message string `json:"message"` Data interface{} `json:"data,omitempty"` Metadata interface{} `json:"metadata,omitempty"` + Page *int `json:"page,omitempty"` + Total *int `json:"total,omitempty"` Timestamp time.Time `json:"timestamp"` } func NewAPIResponse( status Status, message string, data interface{}, metadata interface{}, + page *int, total *int, ) APIResponse { return APIResponse{ @@ -32,6 +35,8 @@ func NewAPIResponse( Data: data, Metadata: metadata, Timestamp: time.Now(), + Page: page, + Total: total, } } func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}) error { @@ -41,7 +46,18 @@ func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interfac } else { apiStatus = Error } - apiRes := NewAPIResponse(apiStatus, message, data, metadata) + apiRes := NewAPIResponse(apiStatus, message, data, metadata, nil, nil) + + return c.Status(status).JSON(apiRes) +} +func WritePaginatedJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}, page int, total int) error { + var apiStatus Status + if status >= 200 && status <= 299 { + apiStatus = Success + } else { + apiStatus = Error + } + apiRes := NewAPIResponse(apiStatus, message, data, metadata, &page, &total) return c.Status(status).JSON(apiRes) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 910e4eb..a8496db 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -65,11 +65,8 @@ func (a *App) initAppRoutes() { a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist) a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile) - // Wallet Routes a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet) - a.fiber.Get("/wallet", h.GetAllWallets) - a.fiber.Get("/wallet/:id", h.GetWalletByID) - a.fiber.Patch("/wallet/:id", h.UpdateWalletActive) + a.fiber.Post("/user/search", a.authMiddleware, handlers.SearchUserByNameOrPhone(a.logger, a.userSvc, a.validator)) // Referral Routes a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferralCode) @@ -77,9 +74,54 @@ func (a *App) initAppRoutes() { a.fiber.Get("/referral/settings", h.GetReferralSettings) a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) + a.fiber.Get("/cashiers", a.authMiddleware, handlers.GetAllCashiers(a.logger, a.userSvc, a.validator)) + a.fiber.Post("/cashiers", a.authMiddleware, handlers.CreateCashier(a.logger, a.userSvc, a.branchSvc, a.validator)) + a.fiber.Put("/cashiers/:id", a.authMiddleware, handlers.UpdateCashier(a.logger, a.userSvc, a.validator)) + // + + a.fiber.Get("/managers", a.authMiddleware, handlers.GetAllManagers(a.logger, a.userSvc, a.validator)) + a.fiber.Post("/managers", a.authMiddleware, handlers.CreateManager(a.logger, a.userSvc, a.validator)) + a.fiber.Put("/managers/:id", a.authMiddleware, handlers.UPdateManagers(a.logger, a.userSvc, a.validator)) + a.fiber.Get("/manager/:id/branch", a.authMiddleware, handlers.GetBranchByManagerID(a.logger, a.branchSvc, a.validator)) + + a.fiber.Get("/company/:id/branch", a.authMiddleware, handlers.GetBranchByCompanyID(a.logger, a.branchSvc, a.validator)) + + a.fiber.Get("/prematch/odds/:event_id", handlers.GetPrematchOdds(a.logger, a.prematchSvc)) + a.fiber.Get("/prematch/odds", handlers.GetALLPrematchOdds(a.logger, a.prematchSvc)) + a.fiber.Get("/prematch/odds/upcoming/:upcoming_id/market/:market_id", handlers.GetRawOddsByMarketID(a.logger, a.prematchSvc)) + + a.fiber.Get("/prematch/events/:id", handlers.GetUpcomingEventByID(a.logger, a.eventSvc)) + a.fiber.Get("/prematch/events", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc)) + a.fiber.Get("/prematch/odds/upcoming/:upcoming_id", handlers.GetPrematchOddsByUpcomingID(a.logger, a.prematchSvc)) + // Swagger a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) + // Branch + a.fiber.Post("/branch", a.authMiddleware, handlers.CreateBranch(a.logger, a.branchSvc, a.walletSvc, a.validator)) + a.fiber.Get("/branch", a.authMiddleware, handlers.GetAllBranches(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/branch/:id", a.authMiddleware, handlers.GetBranchByID(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/branch/:id/bets", a.authMiddleware, handlers.GetBetByBranchID(a.logger, a.betSvc, a.validator)) + a.fiber.Put("/branch/:id", a.authMiddleware, handlers.UpdateBranch(a.logger, a.branchSvc, a.validator)) + a.fiber.Delete("/branch/:id", a.authMiddleware, handlers.DeleteBranch(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/search/branch", a.authMiddleware, handlers.SearchBranch(a.logger, a.branchSvc, a.validator)) + // /branch/search + // branch/wallet + + // Branch Operation + a.fiber.Get("/supportedOperation", a.authMiddleware, handlers.GetAllSupportedOperations(a.logger, a.branchSvc, a.validator)) + a.fiber.Post("/supportedOperation", a.authMiddleware, handlers.CreateSupportedOperation(a.logger, a.branchSvc, a.validator)) + a.fiber.Post("/operation", a.authMiddleware, handlers.CreateBranchOperation(a.logger, a.branchSvc, a.validator)) + a.fiber.Get("/branch/:id/operation", a.authMiddleware, handlers.GetBranchOperations(a.logger, a.branchSvc, a.validator)) + a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, handlers.DeleteBranchOperation(a.logger, a.branchSvc, a.validator)) + + // Company + a.fiber.Post("/company", a.authMiddleware, a.SuperAdminOnly, handlers.CreateCompany(a.logger, a.companySvc, a.walletSvc, a.validator)) + a.fiber.Get("/company", a.authMiddleware, a.SuperAdminOnly, handlers.GetAllCompanies(a.logger, a.companySvc, a.validator)) + a.fiber.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.GetCompanyByID(a.logger, a.companySvc, a.validator)) + a.fiber.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.UpdateCompany(a.logger, a.companySvc, a.validator)) + a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, handlers.DeleteCompany(a.logger, a.companySvc, a.validator)) + // Ticket Routes a.fiber.Post("/ticket", h.CreateTicket) a.fiber.Get("/ticket", h.GetAllTickets) @@ -89,10 +131,23 @@ func (a *App) initAppRoutes() { a.fiber.Post("/bet", h.CreateBet) a.fiber.Get("/bet", h.GetAllBet) a.fiber.Get("/bet/:id", h.GetBetByID) + a.fiber.Get("/bet/cashout/:id", a.authMiddleware, handlers.GetBetByCashoutID(a.logger, a.betSvc, a.validator)) a.fiber.Patch("/bet/:id", h.UpdateCashOut) a.fiber.Delete("/bet/:id", h.DeleteBet) - // Transaction Routes + // Wallet + a.fiber.Get("/wallet", handlers.GetAllWallets(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/wallet/:id", handlers.GetWalletByID(a.logger, a.walletSvc, a.validator)) + a.fiber.Put("/wallet/:id", handlers.UpdateWalletActive(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/branchWallet", a.authMiddleware, handlers.GetAllBranchWallets(a.logger, a.walletSvc, a.validator)) + + // Transfer + // /transfer/wallet - transfer from one wallet to another wallet + a.fiber.Post("/transfer/wallet/:id", a.authMiddleware, handlers.TransferToWallet(a.logger, a.walletSvc, a.branchSvc, a.validator)) + a.fiber.Get("/transfer/wallet/:id", a.authMiddleware, handlers.GetTransfersByWallet(a.logger, a.walletSvc, a.validator)) + a.fiber.Post("/transfer/refill/:id", a.authMiddleware, handlers.RefillWallet(a.logger, a.walletSvc, a.validator)) + + // Transactions /transactions a.fiber.Post("/transaction", h.CreateTransaction) a.fiber.Get("/transaction", h.GetAllTransactions) a.fiber.Get("/transaction/:id", h.GetTransactionByID) @@ -108,6 +163,7 @@ func (a *App) initAppRoutes() { a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback) } +///user/profile get // @Router /user/resetPassword [post] // @Router /user/sendResetCode [post] // @Router /user/register [post] diff --git a/sqlc.yaml b/sqlc.yaml index 6f394a6..dca5591 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -9,10 +9,19 @@ sql: sql_package: "pgx/v5" out: "./gen/db" emit_exported_queries: true - emit_json_tags: false + emit_json_tags: true overrides: - db_type: "uuid" go_type: "github.com/google/uuid.UUID" - db_type: "uuid" go_type: "github.com/google/uuid.NullUUID" nullable: true + - column: "bet_with_outcomes.outcomes" + go_type: + type: "BetOutcome" + slice: true + - column: "ticket_with_outcomes.outcomes" + go_type: + type: "TicketOutcome" + slice: true +