Merge branch 'main' into feature/referal

This commit is contained in:
Samuel Tariku 2025-04-23 07:45:41 +03:00
commit fa3b6a24d3
106 changed files with 15183 additions and 1097 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ coverage
.env
tmp
build
*.log
db.sql

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"Cashout"
]
}

View File

@ -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)
}
}

View File

@ -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;

View File

@ -13,7 +13,10 @@ CREATE TABLE IF NOT EXISTS users (
--
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,
@ -36,7 +39,6 @@ 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,
@ -46,45 +48,78 @@ CREATE TABLE IF NOT EXISTS bets (
phone_number VARCHAR(255) NOT NULL,
branch_id BIGINT,
user_id BIGINT,
cashed_out BOOLEAN DEFAULT FALSE,
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,
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,18 +161,137 @@ 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 (
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',
@ -149,3 +305,84 @@ INSERT INTO users (
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------------------------------------------------

View File

@ -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;

86
db/query/branch.sql Normal file
View File

@ -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;

24
db/query/company.sql Normal file
View File

@ -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;

207
db/query/events.sql Normal file
View File

@ -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;

121
db/query/odds.sql Normal file
View File

@ -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;

View File

@ -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;

View File

@ -1,5 +1,5 @@
-- 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;
@ -7,6 +7,9 @@ SELECT * FROM transactions;
-- 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;

View File

@ -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;

View File

@ -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);
SET password = $1,
updated_at = $4
WHERE (
email = $2
OR phone_number = $3
);

View File

@ -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,
@ -28,13 +41,28 @@ SELECT
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;
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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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) {

View File

@ -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
}

551
gen/db/branch.sql.go Normal file
View File

@ -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
}

122
gen/db/company.sql.go Normal file
View File

@ -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
}

96
gen/db/copyfrom.go Normal file
View File

@ -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})
}

View File

@ -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 {

516
gen/db/events.sql.go Normal file
View File

@ -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
}

View File

@ -56,59 +56,210 @@ 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 `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
SentTo string
Medium string
OtpFor string
Otp string
Used bool
UsedAt pgtype.Timestamptz
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 {
ID int64
ReferralCode string
@ -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"`
}

View File

@ -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) {

375
gen/db/odds.sql.go Normal file
View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
@ -120,25 +201,26 @@ SELECT
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
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 {

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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
}

1
internal/domain/bank.go Normal file
View File

@ -0,0 +1 @@
package domain

View File

@ -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()

View File

@ -6,9 +6,56 @@ type Branch struct {
Location string
WalletID int64
BranchManagerID int64
CompanyID int64
IsSelfOwned bool
IsSupportingSportBook bool
IsSupportingVirtual bool
IsSupportingGameZone 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
}

1
internal/domain/chapa.go Normal file
View File

@ -0,0 +1 @@
package domain

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

47
internal/domain/odds.go Normal file
View File

@ -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"`
}

View File

@ -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
}

View File

@ -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

View File

@ -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
PaymentMethod PaymentMethod
ReceiverWalletID int64
SenderWalletID ValidInt64
CashierID ValidInt64
CreatedAt time.Time
UpdatedAt time.Time
}
type CreateTransfer struct {
Amount Currency
Verified bool
WalletID int64
ReceiverWalletID int64
SenderWalletID ValidInt64
CashierID ValidInt64
Type TransferType
PaymentMethod PaymentMethod
}

View File

@ -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

View File

@ -7,6 +7,7 @@ type Wallet struct {
Balance Currency
IsWithdraw bool
IsBettable bool
IsTransferable bool
IsActive bool
UserID int64
UpdatedAt time.Time
@ -33,9 +34,23 @@ 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
IsTransferable bool
UserID int64
}

View File

@ -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
@ -84,9 +193,23 @@ 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,
},
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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

249
internal/repository/odds.go Normal file
View File

@ -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
}

View File

@ -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)
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))
}
rows, err := s.queries.CreateTicketOutcome(ctx, dbParams)
if err != nil {
return domain.Ticket{}, err
return rows, err
}
return convertDBTicket(ticket), nil
return rows, nil
}
func (s *Store) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) {
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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -13,6 +13,7 @@ func convertDBWallet(wallet dbgen.Wallet) domain.Wallet {
Balance: domain.Currency(wallet.Balance),
IsWithdraw: wallet.IsWithdraw,
IsBettable: wallet.IsBettable,
IsTransferable: wallet.IsTransferable,
IsActive: wallet.IsActive,
UserID: wallet.UserID,
UpdatedAt: wallet.UpdatedAt.Time,
@ -24,6 +25,7 @@ func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams {
return dbgen.CreateWalletParams{
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,

View File

@ -22,6 +22,7 @@ type LoginSuccess struct {
UserId int64
Role domain.Role
RfToken string
BranchId int64
}
func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) {
@ -51,37 +52,35 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -1 +0,0 @@
package transfer

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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,
}

View File

@ -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)

View File

@ -0,0 +1 @@
package wallet

View File

@ -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
}

View File

@ -1,102 +1,13 @@
package wallet
import (
"context"
"errors"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type Service struct {
walletStore WalletStore
transferStore TransferStore
}
func NewService(walletStore WalletStore) *Service {
func NewService(walletStore WalletStore, transferStore TransferStore) *Service {
return &Service{
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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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 BetRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
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.BetStatus `json:"status" example:"1"`
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.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
// 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)
}
bet, err := h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Outcomes: req.Outcomes,
Amount: domain.Currency(req.Amount),
// 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: branchID,
Valid: isShopBet,
Value: branch.ID,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: !isShopBet,
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(),
})
}
if err != nil {
h.logger.Error("Failed to create bet", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet")
h.logger.Error("CreateBetReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
// TODO: Reduce amount from the branch wallet (assuming walletSvc integration)
// This would typically be done here or in the bet service
//
// 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)
}
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)
// 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
}
}
res := convertBet(bet)
return response.WriteJSON(c, fiber.StatusOK, "Bet created successfully", res, nil)
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,
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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),
// 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 := 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 {
h.logger.Error("Failed to create ticket", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create ticket")
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

View File

@ -14,7 +14,7 @@ type TransactionRes struct {
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,14 +27,13 @@ type TransactionRes struct {
}
type CreateTransactionReq struct {
CashoutID string `json:"cashout_id" example:"191212"`
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"`
// Payment Details for bank
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
@ -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,41 +72,56 @@ 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)
if user.Role == domain.RoleCustomer {
logger.Error("CreateTransactionReq failed")
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "unauthorized access",
})
}
// 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)
}
branchID = branch.ID
} 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
}
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")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
if valErrs, ok := h.validator.Validate(c, req); !ok {
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)
}
isDeposit := req.PaymentOption == domain.BANK
transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
Amount: domain.Currency(req.Amount),
BranchID: req.BranchID,
CashierID: req.CashierID,
transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
BranchID: branchID,
CashierID: userID,
Amount: domain.ToCurrency(req.Amount),
BetID: req.BetID,
PaymentOption: req.PaymentOption,
Type: domain.TransactionType(req.Type),
PaymentOption: domain.PaymentOption(req.PaymentOption),
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BankCode: req.BankCode,
@ -115,14 +130,17 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
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")
logger.Error("CreateTransactionReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
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)
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,10 +159,38 @@ 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())
// Get user_id from middleware
userID := c.Locals("user_id").(int64)
// Fetch user details
user, err := userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get transactions", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transactions")
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))
@ -152,7 +198,8 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
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
@ -210,8 +257,8 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
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")
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 {

View File

@ -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)
}
}

View File

@ -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)
}
}

Some files were not shown because too many files have changed in this diff Show More