manager resp struct fix+merge conflict fix
This commit is contained in:
commit
25ded17b09
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Builder stage
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build -ldflags="-s -w" -o ./bin/web ./cmd/main.go
|
||||
|
||||
# Runner stage
|
||||
FROM alpine:3.21 AS runner
|
||||
WORKDIR /app
|
||||
COPY .env .
|
||||
COPY --from=builder /app/bin/web /app/bin/web
|
||||
RUN apk add --no-cache ca-certificates
|
||||
EXPOSE ${PORT}
|
||||
CMD ["/app/bin/web"]
|
||||
|
|
@ -77,15 +77,14 @@ func main() {
|
|||
userSvc := user.NewService(store, store, mockSms, mockEmail)
|
||||
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(cfg.Bet365Token, store)
|
||||
resultSvc := result.NewService(store, cfg, logger)
|
||||
oddsSvc := odds.New(store, cfg, logger)
|
||||
ticketSvc := ticket.NewService(store)
|
||||
betSvc := bet.NewService(store)
|
||||
walletSvc := wallet.NewService(store, store)
|
||||
transactionSvc := transaction.NewService(store)
|
||||
branchSvc := branch.NewService(store)
|
||||
companySvc := company.NewService(store)
|
||||
|
||||
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger)
|
||||
resultSvc := result.NewService(store, cfg, logger, *betSvc)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
referalRepo := repository.NewReferralRepository(store)
|
||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||
|
|
@ -108,6 +107,7 @@ func main() {
|
|||
)
|
||||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||
httpserver.StartTicketCrons(*ticketSvc)
|
||||
|
||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||
JwtAccessKey: cfg.JwtKey,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ CREATE TABLE IF NOT EXISTS wallets (
|
|||
CREATE TABLE IF NOT EXISTS customer_wallets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL,
|
||||
company_id BIGINT NOT NULL,
|
||||
regular_wallet_id BIGINT NOT NULL,
|
||||
static_wallet_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
|
@ -234,12 +233,17 @@ CREATE TABLE companies (
|
|||
wallet_id BIGINT NOT NULL
|
||||
);
|
||||
-- Views
|
||||
CREATE VIEW companies_with_wallets AS
|
||||
CREATE VIEW companies_details AS
|
||||
SELECT companies.*,
|
||||
wallets.balance,
|
||||
wallets.is_active
|
||||
wallets.is_active,
|
||||
users.first_name AS admin_first_name,
|
||||
users.last_name AS admin_last_name,
|
||||
users.phone_number AS admin_phone_number
|
||||
FROM companies
|
||||
JOIN wallets ON wallets.id = companies.wallet_id;
|
||||
JOIN wallets ON wallets.id = companies.wallet_id
|
||||
JOIN users ON users.id = companies.admin_id;
|
||||
;
|
||||
CREATE VIEW branch_details AS
|
||||
SELECT branches.*,
|
||||
CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
|
||||
|
|
@ -290,11 +294,11 @@ ALTER TABLE branch_operations
|
|||
ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||
ALTER TABLE branch_cashiers
|
||||
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
||||
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||
ALTER TABLE companies
|
||||
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id);
|
||||
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
||||
----------------------------------------------seed data-------------------------------------------------------------
|
||||
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
|
@ -340,15 +344,43 @@ INSERT INTO users (
|
|||
suspended_at,
|
||||
suspended
|
||||
)
|
||||
VALUES (
|
||||
'Test',
|
||||
'Admin',
|
||||
'test.admin@gmail.com',
|
||||
'0988554466',
|
||||
crypt('password123', gen_salt('bf'))::bytea,
|
||||
'admin',
|
||||
TRUE,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
FALSE
|
||||
);
|
||||
INSERT INTO users (
|
||||
first_name,
|
||||
last_name,
|
||||
email,
|
||||
phone_number,
|
||||
password,
|
||||
role,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended_at,
|
||||
suspended
|
||||
)
|
||||
VALUES (
|
||||
'Samuel',
|
||||
'Tariku',
|
||||
'cybersamt@gmail.com',
|
||||
NULL,
|
||||
'0911111111',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'super_admin',
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
|
|
@ -372,11 +404,11 @@ VALUES (
|
|||
'Kirubel',
|
||||
'Kibru',
|
||||
'kirubeljkl679 @gmail.com',
|
||||
NULL,
|
||||
'0911554486',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'super_admin',
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
|
|
@ -384,8 +416,7 @@ VALUES (
|
|||
);
|
||||
INSERT INTO supported_operations (name, description)
|
||||
VALUES ('SportBook', 'Sportbook operations'),
|
||||
('Virtual', 'Virtual operations'),
|
||||
('GameZone', 'GameZone operations');
|
||||
('Virtual', 'Virtual operations');
|
||||
INSERT INTO wallets (
|
||||
balance,
|
||||
is_withdraw,
|
||||
|
|
@ -405,4 +436,54 @@ VALUES (
|
|||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO companies (
|
||||
name,
|
||||
admin_id,
|
||||
wallet_id
|
||||
)
|
||||
values (
|
||||
'Test Company',
|
||||
2,
|
||||
1
|
||||
);
|
||||
INSERT INTO wallets (
|
||||
balance,
|
||||
is_withdraw,
|
||||
is_bettable,
|
||||
is_transferable,
|
||||
user_id,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
10000,
|
||||
TRUE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
2,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO branches (
|
||||
name,
|
||||
location,
|
||||
wallet_id,
|
||||
branch_manager_id,
|
||||
company_id,
|
||||
is_self_owned,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
values (
|
||||
'Test Branch',
|
||||
'Addis Ababa',
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -62,21 +62,25 @@ WHERE branch_id = $1;
|
|||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE event_id = $1;
|
||||
|
||||
-- name: GetBetOutcomeByBetID :many
|
||||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE bet_id = $1;
|
||||
-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
-- name: UpdateBetOutcomeStatus :exec
|
||||
-- name: UpdateBetOutcomeStatus :one
|
||||
UPDATE bet_outcomes
|
||||
SET status = $1
|
||||
WHERE id = $2;
|
||||
WHERE id = $2
|
||||
RETURNING *;
|
||||
-- name: UpdateStatus :exec
|
||||
UPDATE bets
|
||||
SET status = $2,
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
WHERE id = $2;
|
||||
-- name: DeleteBet :exec
|
||||
DELETE FROM bets
|
||||
WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -55,15 +55,6 @@ 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 = COALESCE(sqlc.narg(name), name),
|
||||
|
|
|
|||
15
db/query/cashier.sql
Normal file
15
db/query/cashier.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
-- 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.*,
|
||||
branch_id
|
||||
FROM branch_cashiers
|
||||
JOIN users ON branch_cashiers.user_id = users.id;
|
||||
-- name: GetCashierByID :one
|
||||
SELECT users.*,
|
||||
branch_id
|
||||
FROM branch_cashiers
|
||||
JOIN users ON branch_cashiers.user_id = $1;
|
||||
|
|
@ -8,14 +8,14 @@ VALUES ($1, $2, $3)
|
|||
RETURNING *;
|
||||
-- name: GetAllCompanies :many
|
||||
SELECT *
|
||||
FROM companies_with_wallets;
|
||||
FROM companies_details;
|
||||
-- name: GetCompanyByID :one
|
||||
SELECT *
|
||||
FROM companies_with_wallets
|
||||
FROM companies_details
|
||||
WHERE id = $1;
|
||||
-- name: SearchCompanyByName :many
|
||||
SELECT *
|
||||
FROM companies_with_wallets
|
||||
FROM companies_details
|
||||
WHERE name ILIKE '%' || $1 || '%';
|
||||
-- name: UpdateCompany :one
|
||||
UPDATE companies
|
||||
|
|
|
|||
|
|
@ -158,9 +158,7 @@ SELECT id,
|
|||
status,
|
||||
fetched_at
|
||||
FROM events
|
||||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND start_time < now()
|
||||
WHERE start_time < now()
|
||||
ORDER BY start_time ASC;
|
||||
-- name: GetTotalEvents :one
|
||||
SELECT COUNT(*)
|
||||
|
|
@ -168,12 +166,20 @@ FROM events
|
|||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND (
|
||||
league_id = $1
|
||||
OR $1 IS NULL
|
||||
league_id = sqlc.narg('league_id')
|
||||
OR sqlc.narg('league_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
sport_id = $2
|
||||
OR $2 IS NULL
|
||||
sport_id = sqlc.narg('sport_id')
|
||||
OR sqlc.narg('sport_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < sqlc.narg('last_start_time')
|
||||
OR sqlc.narg('last_start_time') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time > sqlc.narg('first_start_time')
|
||||
OR sqlc.narg('first_start_time') IS NULL
|
||||
);
|
||||
-- name: GetPaginatedUpcomingEvents :many
|
||||
SELECT id,
|
||||
|
|
@ -196,15 +202,23 @@ FROM events
|
|||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND (
|
||||
league_id = $3
|
||||
OR $3 IS NULL
|
||||
league_id = sqlc.narg('league_id')
|
||||
OR sqlc.narg('league_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
sport_id = $4
|
||||
OR $4 IS NULL
|
||||
sport_id = sqlc.narg('sport_id')
|
||||
OR sqlc.narg('sport_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < sqlc.narg('last_start_time')
|
||||
OR sqlc.narg('last_start_time') IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time > sqlc.narg('first_start_time')
|
||||
OR sqlc.narg('first_start_time') IS NULL
|
||||
)
|
||||
ORDER BY start_time ASC
|
||||
LIMIT $1 OFFSET $2;
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
-- name: GetUpcomingByID :one
|
||||
SELECT id,
|
||||
sport_id,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,71 @@
|
|||
-- name: CreateNotification :one
|
||||
INSERT INTO notifications (
|
||||
id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, timestamp, metadata
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
|
||||
) RETURNING *;
|
||||
|
||||
id,
|
||||
recipient_id,
|
||||
type,
|
||||
level,
|
||||
error_severity,
|
||||
reciever,
|
||||
is_read,
|
||||
delivery_status,
|
||||
delivery_channel,
|
||||
payload,
|
||||
priority,
|
||||
timestamp,
|
||||
metadata
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12,
|
||||
$13
|
||||
)
|
||||
RETURNING *;
|
||||
-- name: GetNotification :one
|
||||
SELECT * FROM notifications WHERE id = $1 LIMIT 1;
|
||||
|
||||
SELECT *
|
||||
FROM notifications
|
||||
WHERE id = $1
|
||||
LIMIT 1;
|
||||
-- name: GetAllNotifications :many
|
||||
SELECT *
|
||||
FROM notifications
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT $1 OFFSET $2;
|
||||
-- name: ListNotifications :many
|
||||
SELECT * FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3;
|
||||
|
||||
SELECT *
|
||||
FROM notifications
|
||||
WHERE recipient_id = $1
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT $2 OFFSET $3;
|
||||
-- name: CountUnreadNotifications :one
|
||||
SELECT count(id)
|
||||
FROM notifications
|
||||
WHERE recipient_id = $1
|
||||
AND is_read = false;
|
||||
-- name: UpdateNotificationStatus :one
|
||||
UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING *;
|
||||
|
||||
UPDATE notifications
|
||||
SET delivery_status = $2,
|
||||
is_read = $3,
|
||||
metadata = $4
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
-- name: ListFailedNotifications :many
|
||||
SELECT * FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1;
|
||||
|
||||
SELECT *
|
||||
FROM notifications
|
||||
WHERE delivery_status = 'failed'
|
||||
AND timestamp < NOW() - INTERVAL '1 hour'
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT $1;
|
||||
-- name: ListRecipientIDsByReceiver :many
|
||||
SELECT recipient_id FROM notifications WHERE reciever = $1;
|
||||
SELECT recipient_id
|
||||
FROM notifications
|
||||
WHERE reciever = $1;
|
||||
|
|
@ -94,23 +94,17 @@ 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
|
||||
SELECT o.*
|
||||
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';
|
||||
-- name: GetPaginatedPrematchOddsByUpcomingID :many
|
||||
SELECT o.*
|
||||
FROM odds o
|
||||
JOIN events e ON o.fi = e.id
|
||||
WHERE e.id = $1
|
||||
|
|
@ -118,4 +112,4 @@ WHERE e.id = $1
|
|||
AND e.status = 'upcoming'
|
||||
AND o.is_active = true
|
||||
AND o.source = 'b365api'
|
||||
LIMIT $2 OFFSET $3;
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
|
|
@ -66,7 +66,7 @@ wHERE (
|
|||
company_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
LIMIT $3 OFFSET $4;
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
-- name: GetTotalUsers :one
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
|
|
@ -93,23 +93,30 @@ SELECT id,
|
|||
suspended_at,
|
||||
company_id
|
||||
FROM users
|
||||
WHERE first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%';
|
||||
WHERE (
|
||||
first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%'
|
||||
)
|
||||
AND (
|
||||
role = sqlc.narg('role')
|
||||
OR sqlc.narg('role') IS NULL
|
||||
)
|
||||
AND (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
);
|
||||
-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET first_name = $1,
|
||||
last_name = $2,
|
||||
email = $3,
|
||||
phone_number = $4,
|
||||
role = $5,
|
||||
updated_at = $6
|
||||
WHERE id = $7;
|
||||
suspended = $3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $4;
|
||||
-- name: UpdateUserCompany :exec
|
||||
UPDATE users
|
||||
SET company_id = $1
|
||||
WHERE id = $2;
|
||||
|
||||
-- name: SuspendUser :exec
|
||||
UPDATE users
|
||||
SET suspended = $1,
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ RETURNING *;
|
|||
-- name: CreateCustomerWallet :one
|
||||
INSERT INTO customer_wallets (
|
||||
customer_id,
|
||||
company_id,
|
||||
regular_wallet_id,
|
||||
static_wallet_id
|
||||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING *;
|
||||
-- name: GetAllWallets :many
|
||||
SELECT *
|
||||
|
|
@ -30,7 +29,6 @@ WHERE user_id = $1;
|
|||
-- name: GetCustomerWallet :one
|
||||
SELECT cw.id,
|
||||
cw.customer_id,
|
||||
cw.company_id,
|
||||
rw.id AS regular_id,
|
||||
rw.balance AS regular_balance,
|
||||
sw.id AS static_id,
|
||||
|
|
@ -41,8 +39,7 @@ SELECT cw.id,
|
|||
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;
|
||||
-- name: GetAllBranchWallets :many
|
||||
SELECT wallets.id,
|
||||
wallets.balance,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ services:
|
|||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
migrate:
|
||||
image: migrate/migrate
|
||||
volumes:
|
||||
|
|
@ -32,6 +35,37 @@ services:
|
|||
networks:
|
||||
- app
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: runner
|
||||
ports:
|
||||
- ${PORT}:8080
|
||||
environment:
|
||||
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
||||
depends_on:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
networks:
|
||||
- app
|
||||
command: ["/app/bin/web"]
|
||||
|
||||
|
||||
test:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
volumes:
|
||||
- .:/app
|
||||
command: ["tail", "-f", "/dev/null"]
|
||||
networks:
|
||||
- app
|
||||
|
||||
networks:
|
||||
app:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
787
docs/docs.go
787
docs/docs.go
|
|
@ -129,6 +129,106 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/admin/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single admin by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Get admin by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.AdminRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update Admin",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Update Admin",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update Admin",
|
||||
"name": "admin",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateAdminReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/alea-games/launch": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -687,7 +787,7 @@ const docTemplate = `{
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -724,7 +824,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
||||
"$ref": "#/definitions/domain.CreateBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -732,7 +832,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -776,7 +876,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -820,7 +920,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -1169,7 +1269,54 @@ const docTemplate = `{
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/branch/{id}/cashier": {
|
||||
"get": {
|
||||
"description": "Gets branch cashiers",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"branch"
|
||||
],
|
||||
"summary": "Gets branch cashiers",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Branch ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.GetCashierRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1324,6 +1471,56 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/cashier/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single cashier by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"cashier"
|
||||
],
|
||||
"summary": "Get cashier by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cashiers": {
|
||||
"get": {
|
||||
"description": "Get all cashiers",
|
||||
|
|
@ -1456,7 +1653,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateUserReq"
|
||||
"$ref": "#/definitions/handlers.updateCashierReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -1907,6 +2104,54 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"/managers/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single manager by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"manager"
|
||||
],
|
||||
"summary": "Get manager by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ManagersRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update Managers",
|
||||
"consumes": [
|
||||
|
|
@ -1916,7 +2161,7 @@ const docTemplate = `{
|
|||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Managers"
|
||||
"manager"
|
||||
],
|
||||
"summary": "Update Managers",
|
||||
"parameters": [
|
||||
|
|
@ -1926,7 +2171,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateUserReq"
|
||||
"$ref": "#/definitions/handlers.updateManagerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2041,6 +2286,18 @@ const docTemplate = `{
|
|||
"description": "Sport ID Filter",
|
||||
"name": "sport_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Start Time",
|
||||
"name": "first_start_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "End Time",
|
||||
"name": "last_start_time",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -2298,6 +2555,52 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/random/bet": {
|
||||
"post": {
|
||||
"description": "Generate a random bet",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"bet"
|
||||
],
|
||||
"summary": "Generate a random bet",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Create Random bet",
|
||||
"name": "createBet",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.RandomBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/referral/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3093,6 +3396,50 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/delete/{id}": {
|
||||
"delete": {
|
||||
"description": "Delete a user by their ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete user by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/profile": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3389,7 +3736,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3413,6 +3760,52 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/suspend": {
|
||||
"post": {
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Suspend or unsuspend a user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"name": "updateUserSuspend",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/wallet": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3881,6 +4274,65 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.BetRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"cashed_id": {
|
||||
"type": "string",
|
||||
"example": "21234"
|
||||
},
|
||||
"cashed_out": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"is_shop_bet": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.BetOutcome"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBank": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -3948,6 +4400,58 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateBetOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.CreateBetOutcomeReq"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4076,9 +4580,11 @@ const docTemplate = `{
|
|||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
4,
|
||||
5
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"OUTCOME_STATUS_ERROR": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_VOID": "Give Back"
|
||||
},
|
||||
|
|
@ -4087,7 +4593,8 @@ const docTemplate = `{
|
|||
"OUTCOME_STATUS_WIN",
|
||||
"OUTCOME_STATUS_LOSS",
|
||||
"OUTCOME_STATUS_VOID",
|
||||
"OUTCOME_STATUS_HALF"
|
||||
"OUTCOME_STATUS_HALF",
|
||||
"OUTCOME_STATUS_ERROR"
|
||||
]
|
||||
},
|
||||
"domain.PaymentOption": {
|
||||
|
|
@ -4133,6 +4640,23 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RandomBetReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"branch_id",
|
||||
"number_of_bets"
|
||||
],
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"number_of_bets": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RawOddsByMarketID": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4542,65 +5066,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.BetRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"cashed_id": {
|
||||
"type": "string",
|
||||
"example": "21234"
|
||||
},
|
||||
"cashed_out": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"is_shop_bet": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.BetOutcome"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.BranchDetailRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4762,58 +5227,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBetOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBranchOperationReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5074,10 +5487,6 @@ const docTemplate = `{
|
|||
"handlers.CustomerWalletRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -5113,6 +5522,53 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.GetCashierRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"suspended_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ManagersRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5245,8 +5701,11 @@ const docTemplate = `{
|
|||
"handlers.SearchUserByNameOrPhoneReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"searchString": {
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -5463,6 +5922,34 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"suspended",
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 123
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateWalletActiveReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -5655,9 +6142,51 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateUserReq": {
|
||||
"handlers.updateAdminReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateCashierReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateManagerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
|
|
|
|||
|
|
@ -121,6 +121,106 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/admin/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single admin by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Get admin by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.AdminRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update Admin",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Update Admin",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update Admin",
|
||||
"name": "admin",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateAdminReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/alea-games/launch": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -679,7 +779,7 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -716,7 +816,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
||||
"$ref": "#/definitions/domain.CreateBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -724,7 +824,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -768,7 +868,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -812,7 +912,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -1161,7 +1261,54 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/branch/{id}/cashier": {
|
||||
"get": {
|
||||
"description": "Gets branch cashiers",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"branch"
|
||||
],
|
||||
"summary": "Gets branch cashiers",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Branch ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.GetCashierRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1316,6 +1463,56 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/cashier/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single cashier by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"cashier"
|
||||
],
|
||||
"summary": "Get cashier by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cashiers": {
|
||||
"get": {
|
||||
"description": "Get all cashiers",
|
||||
|
|
@ -1448,7 +1645,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateUserReq"
|
||||
"$ref": "#/definitions/handlers.updateCashierReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -1899,6 +2096,54 @@
|
|||
}
|
||||
},
|
||||
"/managers/{id}": {
|
||||
"get": {
|
||||
"description": "Get a single manager by id",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"manager"
|
||||
],
|
||||
"summary": "Get manager by id",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ManagersRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update Managers",
|
||||
"consumes": [
|
||||
|
|
@ -1908,7 +2153,7 @@
|
|||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Managers"
|
||||
"manager"
|
||||
],
|
||||
"summary": "Update Managers",
|
||||
"parameters": [
|
||||
|
|
@ -1918,7 +2163,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateUserReq"
|
||||
"$ref": "#/definitions/handlers.updateManagerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2033,6 +2278,18 @@
|
|||
"description": "Sport ID Filter",
|
||||
"name": "sport_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Start Time",
|
||||
"name": "first_start_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "End Time",
|
||||
"name": "last_start_time",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -2290,6 +2547,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/random/bet": {
|
||||
"post": {
|
||||
"description": "Generate a random bet",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"bet"
|
||||
],
|
||||
"summary": "Generate a random bet",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Create Random bet",
|
||||
"name": "createBet",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.RandomBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/referral/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3085,6 +3388,50 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/delete/{id}": {
|
||||
"delete": {
|
||||
"description": "Delete a user by their ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete user by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/profile": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3381,7 +3728,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3405,6 +3752,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/suspend": {
|
||||
"post": {
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Suspend or unsuspend a user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"name": "updateUserSuspend",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/wallet": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
@ -3873,6 +4266,65 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.BetRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"cashed_id": {
|
||||
"type": "string",
|
||||
"example": "21234"
|
||||
},
|
||||
"cashed_out": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"is_shop_bet": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.BetOutcome"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBank": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -3940,6 +4392,58 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateBetOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.CreateBetOutcomeReq"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4068,9 +4572,11 @@
|
|||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
4,
|
||||
5
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"OUTCOME_STATUS_ERROR": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_VOID": "Give Back"
|
||||
},
|
||||
|
|
@ -4079,7 +4585,8 @@
|
|||
"OUTCOME_STATUS_WIN",
|
||||
"OUTCOME_STATUS_LOSS",
|
||||
"OUTCOME_STATUS_VOID",
|
||||
"OUTCOME_STATUS_HALF"
|
||||
"OUTCOME_STATUS_HALF",
|
||||
"OUTCOME_STATUS_ERROR"
|
||||
]
|
||||
},
|
||||
"domain.PaymentOption": {
|
||||
|
|
@ -4125,6 +4632,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RandomBetReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"branch_id",
|
||||
"number_of_bets"
|
||||
],
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"number_of_bets": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RawOddsByMarketID": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4534,65 +5058,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.BetRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"cashed_id": {
|
||||
"type": "string",
|
||||
"example": "21234"
|
||||
},
|
||||
"cashed_out": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"is_shop_bet": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.BetOutcome"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.BranchDetailRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4754,58 +5219,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBetOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||
}
|
||||
],
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateBranchOperationReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5066,10 +5479,6 @@
|
|||
"handlers.CustomerWalletRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -5105,6 +5514,53 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.GetCashierRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"suspended_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ManagersRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5237,8 +5693,11 @@
|
|||
"handlers.SearchUserByNameOrPhoneReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"searchString": {
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -5455,6 +5914,34 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"suspended",
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 123
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateWalletActiveReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -5647,9 +6134,51 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateUserReq": {
|
||||
"handlers.updateAdminReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateCashierReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateManagerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,47 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
domain.BetRes:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
branch_id:
|
||||
example: 2
|
||||
type: integer
|
||||
cashed_id:
|
||||
example: "21234"
|
||||
type: string
|
||||
cashed_out:
|
||||
example: false
|
||||
type: boolean
|
||||
full_name:
|
||||
example: John
|
||||
type: string
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
is_shop_bet:
|
||||
example: false
|
||||
type: boolean
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.BetOutcome'
|
||||
type: array
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
total_odds:
|
||||
example: 4.22
|
||||
type: number
|
||||
user_id:
|
||||
example: 2
|
||||
type: integer
|
||||
type: object
|
||||
domain.ChapaSupportedBank:
|
||||
properties:
|
||||
acct_length:
|
||||
|
|
@ -124,6 +165,41 @@ definitions:
|
|||
message:
|
||||
type: string
|
||||
type: object
|
||||
domain.CreateBetOutcomeReq:
|
||||
properties:
|
||||
event_id:
|
||||
example: 1
|
||||
type: integer
|
||||
market_id:
|
||||
example: 1
|
||||
type: integer
|
||||
odd_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.CreateBetReq:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
branch_id:
|
||||
example: 1
|
||||
type: integer
|
||||
full_name:
|
||||
example: John
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.CreateBetOutcomeReq'
|
||||
type: array
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
domain.CreateTransferResponse:
|
||||
properties:
|
||||
data:
|
||||
|
|
@ -211,8 +287,10 @@ definitions:
|
|||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
type: integer
|
||||
x-enum-comments:
|
||||
OUTCOME_STATUS_ERROR: Half Win and Half Given Back
|
||||
OUTCOME_STATUS_HALF: Half Win and Half Given Back
|
||||
OUTCOME_STATUS_VOID: Give Back
|
||||
x-enum-varnames:
|
||||
|
|
@ -221,6 +299,7 @@ definitions:
|
|||
- OUTCOME_STATUS_LOSS
|
||||
- OUTCOME_STATUS_VOID
|
||||
- OUTCOME_STATUS_HALF
|
||||
- OUTCOME_STATUS_ERROR
|
||||
domain.PaymentOption:
|
||||
enum:
|
||||
- 0
|
||||
|
|
@ -252,6 +331,18 @@ definitions:
|
|||
description: BET, WIN, REFUND, JACKPOT_WIN
|
||||
type: string
|
||||
type: object
|
||||
domain.RandomBetReq:
|
||||
properties:
|
||||
branch_id:
|
||||
example: 1
|
||||
type: integer
|
||||
number_of_bets:
|
||||
example: 1
|
||||
type: integer
|
||||
required:
|
||||
- branch_id
|
||||
- number_of_bets
|
||||
type: object
|
||||
domain.RawOddsByMarketID:
|
||||
properties:
|
||||
fetched_at:
|
||||
|
|
@ -534,47 +625,6 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
handlers.BetRes:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
branch_id:
|
||||
example: 2
|
||||
type: integer
|
||||
cashed_id:
|
||||
example: "21234"
|
||||
type: string
|
||||
cashed_out:
|
||||
example: false
|
||||
type: boolean
|
||||
full_name:
|
||||
example: John
|
||||
type: string
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
is_shop_bet:
|
||||
example: false
|
||||
type: boolean
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.BetOutcome'
|
||||
type: array
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
total_odds:
|
||||
example: 4.22
|
||||
type: number
|
||||
user_id:
|
||||
example: 2
|
||||
type: integer
|
||||
type: object
|
||||
handlers.BranchDetailRes:
|
||||
properties:
|
||||
branch_manager_id:
|
||||
|
|
@ -690,41 +740,6 @@ definitions:
|
|||
example: "1234567890"
|
||||
type: string
|
||||
type: object
|
||||
handlers.CreateBetOutcomeReq:
|
||||
properties:
|
||||
event_id:
|
||||
example: 1
|
||||
type: integer
|
||||
market_id:
|
||||
example: 1
|
||||
type: integer
|
||||
odd_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
handlers.CreateBetReq:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
branch_id:
|
||||
example: 1
|
||||
type: integer
|
||||
full_name:
|
||||
example: John
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.CreateBetOutcomeReq'
|
||||
type: array
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
handlers.CreateBranchOperationReq:
|
||||
properties:
|
||||
branch_id:
|
||||
|
|
@ -909,9 +924,6 @@ definitions:
|
|||
type: object
|
||||
handlers.CustomerWalletRes:
|
||||
properties:
|
||||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
customer_id:
|
||||
|
|
@ -937,6 +949,37 @@ definitions:
|
|||
static_updated_at:
|
||||
type: string
|
||||
type: object
|
||||
handlers.GetCashierRes:
|
||||
properties:
|
||||
branch_id:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
email_verified:
|
||||
type: boolean
|
||||
first_name:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
last_login:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
phone_number:
|
||||
type: string
|
||||
phone_verified:
|
||||
type: boolean
|
||||
role:
|
||||
$ref: '#/definitions/domain.Role'
|
||||
suspended:
|
||||
type: boolean
|
||||
suspended_at:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
handlers.ManagersRes:
|
||||
properties:
|
||||
created_at:
|
||||
|
|
@ -1029,8 +1072,10 @@ definitions:
|
|||
type: object
|
||||
handlers.SearchUserByNameOrPhoneReq:
|
||||
properties:
|
||||
searchString:
|
||||
query:
|
||||
type: string
|
||||
role:
|
||||
$ref: '#/definitions/domain.Role'
|
||||
type: object
|
||||
handlers.SupportedOperationRes:
|
||||
properties:
|
||||
|
|
@ -1182,6 +1227,25 @@ definitions:
|
|||
example: true
|
||||
type: boolean
|
||||
type: object
|
||||
handlers.UpdateUserSuspendReq:
|
||||
properties:
|
||||
suspended:
|
||||
example: true
|
||||
type: boolean
|
||||
user_id:
|
||||
example: 123
|
||||
type: integer
|
||||
required:
|
||||
- suspended
|
||||
- user_id
|
||||
type: object
|
||||
handlers.UpdateUserSuspendRes:
|
||||
properties:
|
||||
suspended:
|
||||
type: boolean
|
||||
user_id:
|
||||
type: integer
|
||||
type: object
|
||||
handlers.UpdateWalletActiveReq:
|
||||
properties:
|
||||
is_active:
|
||||
|
|
@ -1314,8 +1378,38 @@ definitions:
|
|||
- access_token
|
||||
- refresh_token
|
||||
type: object
|
||||
handlers.updateUserReq:
|
||||
handlers.updateAdminReq:
|
||||
properties:
|
||||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
first_name:
|
||||
example: John
|
||||
type: string
|
||||
last_name:
|
||||
example: Doe
|
||||
type: string
|
||||
suspended:
|
||||
example: false
|
||||
type: boolean
|
||||
type: object
|
||||
handlers.updateCashierReq:
|
||||
properties:
|
||||
first_name:
|
||||
example: John
|
||||
type: string
|
||||
last_name:
|
||||
example: Doe
|
||||
type: string
|
||||
suspended:
|
||||
example: false
|
||||
type: boolean
|
||||
type: object
|
||||
handlers.updateManagerReq:
|
||||
properties:
|
||||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
first_name:
|
||||
example: John
|
||||
type: string
|
||||
|
|
@ -1431,6 +1525,72 @@ paths:
|
|||
summary: Create Admin
|
||||
tags:
|
||||
- admin
|
||||
/admin/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a single admin by id
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.AdminRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Get admin by id
|
||||
tags:
|
||||
- admin
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update Admin
|
||||
parameters:
|
||||
- description: Update Admin
|
||||
in: body
|
||||
name: admin
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.updateAdminReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Update Admin
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/alea-games/launch:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -1791,7 +1951,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
@ -1814,14 +1974,14 @@ paths:
|
|||
name: createBet
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateBetReq'
|
||||
$ref: '#/definitions/domain.CreateBetReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1878,7 +2038,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1941,7 +2101,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -2110,7 +2270,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
@ -2123,6 +2283,37 @@ paths:
|
|||
summary: Gets bets by its branch id
|
||||
tags:
|
||||
- branch
|
||||
/branch/{id}/cashier:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Gets branch cashiers
|
||||
parameters:
|
||||
- description: Branch ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.GetCashierRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Gets branch cashiers
|
||||
tags:
|
||||
- branch
|
||||
/branch/{id}/operation:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2213,6 +2404,39 @@ paths:
|
|||
summary: Get all branch wallets
|
||||
tags:
|
||||
- wallet
|
||||
/cashier/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a single cashier by id
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Get cashier by id
|
||||
tags:
|
||||
- cashier
|
||||
/cashiers:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2298,7 +2522,7 @@ paths:
|
|||
name: cashier
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.updateUserReq'
|
||||
$ref: '#/definitions/handlers.updateCashierReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -2598,6 +2822,38 @@ paths:
|
|||
tags:
|
||||
- manager
|
||||
/managers/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a single manager by id
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ManagersRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Get manager by id
|
||||
tags:
|
||||
- manager
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -2608,7 +2864,7 @@ paths:
|
|||
name: Managers
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.updateUserReq'
|
||||
$ref: '#/definitions/handlers.updateManagerReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -2630,7 +2886,7 @@ paths:
|
|||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Update Managers
|
||||
tags:
|
||||
- Managers
|
||||
- manager
|
||||
/operation:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -2683,6 +2939,14 @@ paths:
|
|||
in: query
|
||||
name: sport_id
|
||||
type: string
|
||||
- description: Start Time
|
||||
in: query
|
||||
name: first_start_time
|
||||
type: string
|
||||
- description: End Time
|
||||
in: query
|
||||
name: last_start_time
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -2856,6 +3120,36 @@ paths:
|
|||
summary: Retrieve raw odds by Market ID
|
||||
tags:
|
||||
- prematch
|
||||
/random/bet:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Generate a random bet
|
||||
parameters:
|
||||
- description: Create Random bet
|
||||
in: body
|
||||
name: createBet
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.RandomBetReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Generate a random bet
|
||||
tags:
|
||||
- bet
|
||||
/referral/settings:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -3375,6 +3669,35 @@ paths:
|
|||
summary: Check if phone number or email exist
|
||||
tags:
|
||||
- user
|
||||
/user/delete/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Delete a user by their ID
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Delete user by ID
|
||||
tags:
|
||||
- user
|
||||
/user/profile:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -3567,7 +3890,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -3583,6 +3906,36 @@ paths:
|
|||
summary: Get user by id
|
||||
tags:
|
||||
- user
|
||||
/user/suspend:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Suspend or unsuspend a user
|
||||
parameters:
|
||||
- description: Suspend or unsuspend a user
|
||||
in: body
|
||||
name: updateUserSuspend
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UpdateUserSuspendReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UpdateUserSuspendRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Suspend or unsuspend a user
|
||||
tags:
|
||||
- user
|
||||
/user/wallet:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -243,6 +243,48 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
|||
return i, err
|
||||
}
|
||||
|
||||
const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many
|
||||
SELECT id, bet_id, sport_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 bet_outcomes
|
||||
WHERE bet_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetOutcome, error) {
|
||||
rows, err := q.db.Query(ctx, GetBetOutcomeByBetID, betID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BetOutcome
|
||||
for rows.Next() {
|
||||
var i BetOutcome
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.BetID,
|
||||
&i.SportID,
|
||||
&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 GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
|
||||
SELECT id, bet_id, sport_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 bet_outcomes
|
||||
|
|
@ -285,10 +327,11 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec
|
||||
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :one
|
||||
UPDATE bet_outcomes
|
||||
SET status = $1
|
||||
WHERE id = $2
|
||||
RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||
`
|
||||
|
||||
type UpdateBetOutcomeStatusParams struct {
|
||||
|
|
@ -296,9 +339,27 @@ type UpdateBetOutcomeStatusParams struct {
|
|||
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
|
||||
func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutcomeStatusParams) (BetOutcome, error) {
|
||||
row := q.db.QueryRow(ctx, UpdateBetOutcomeStatus, arg.Status, arg.ID)
|
||||
var i BetOutcome
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.BetID,
|
||||
&i.SportID,
|
||||
&i.EventID,
|
||||
&i.OddID,
|
||||
&i.HomeTeamName,
|
||||
&i.AwayTeamName,
|
||||
&i.MarketID,
|
||||
&i.MarketName,
|
||||
&i.Odd,
|
||||
&i.OddName,
|
||||
&i.OddHeader,
|
||||
&i.OddHandicap,
|
||||
&i.Status,
|
||||
&i.Expires,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
||||
|
|
@ -320,17 +381,17 @@ func (q *Queries) UpdateCashOut(ctx context.Context, arg UpdateCashOutParams) er
|
|||
|
||||
const UpdateStatus = `-- name: UpdateStatus :exec
|
||||
UPDATE bets
|
||||
SET status = $2,
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateStatusParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Status int32 `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateStatus(ctx context.Context, arg UpdateStatusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateStatus, arg.ID, arg.Status)
|
||||
_, err := q.db.Exec(ctx, UpdateStatus, arg.Status, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,49 +190,6 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
|
|||
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.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by
|
||||
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.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
); 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
|
||||
|
|
@ -430,50 +387,6 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
|
|||
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.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by
|
||||
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.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
); 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
|
||||
|
|
|
|||
173
gen/db/cashier.sql.go
Normal file
173
gen/db/cashier.sql.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: cashier.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
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.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by,
|
||||
branch_id
|
||||
FROM branch_cashiers
|
||||
JOIN users ON branch_cashiers.user_id = users.id
|
||||
`
|
||||
|
||||
type GetAllCashiersRow 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"`
|
||||
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"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
ReferralCode pgtype.Text `json:"referral_code"`
|
||||
ReferredBy pgtype.Text `json:"referred_by"`
|
||||
BranchID int64 `json:"branch_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllCashiers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetAllCashiersRow
|
||||
for rows.Next() {
|
||||
var i GetAllCashiersRow
|
||||
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.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
&i.BranchID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetCashierByID = `-- name: GetCashierByID :one
|
||||
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.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by,
|
||||
branch_id
|
||||
FROM branch_cashiers
|
||||
JOIN users ON branch_cashiers.user_id = $1
|
||||
`
|
||||
|
||||
type GetCashierByIDRow 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"`
|
||||
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"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
ReferralCode pgtype.Text `json:"referral_code"`
|
||||
ReferredBy pgtype.Text `json:"referred_by"`
|
||||
BranchID int64 `json:"branch_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCashierByID(ctx context.Context, userID int64) (GetCashierByIDRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetCashierByID, userID)
|
||||
var i GetCashierByIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
&i.Password,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
&i.BranchID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by
|
||||
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.CompanyID,
|
||||
&i.SuspendedAt,
|
||||
&i.Suspended,
|
||||
&i.ReferralCode,
|
||||
&i.ReferredBy,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
|
@ -50,19 +50,19 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
const GetAllCompanies = `-- name: GetAllCompanies :many
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active
|
||||
FROM companies_with_wallets
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
FROM companies_details
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesWithWallet, error) {
|
||||
func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesDetail, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllCompanies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CompaniesWithWallet
|
||||
var items []CompaniesDetail
|
||||
for rows.Next() {
|
||||
var i CompaniesWithWallet
|
||||
var i CompaniesDetail
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
|
|
@ -70,6 +70,9 @@ func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesWithWallet, e
|
|||
&i.WalletID,
|
||||
&i.Balance,
|
||||
&i.IsActive,
|
||||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -82,14 +85,14 @@ func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesWithWallet, e
|
|||
}
|
||||
|
||||
const GetCompanyByID = `-- name: GetCompanyByID :one
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active
|
||||
FROM companies_with_wallets
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
FROM companies_details
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesWithWallet, error) {
|
||||
func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail, error) {
|
||||
row := q.db.QueryRow(ctx, GetCompanyByID, id)
|
||||
var i CompaniesWithWallet
|
||||
var i CompaniesDetail
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
|
|
@ -97,25 +100,28 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesWithWa
|
|||
&i.WalletID,
|
||||
&i.Balance,
|
||||
&i.IsActive,
|
||||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const SearchCompanyByName = `-- name: SearchCompanyByName :many
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active
|
||||
FROM companies_with_wallets
|
||||
SELECT id, name, admin_id, wallet_id, balance, is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
FROM companies_details
|
||||
WHERE name ILIKE '%' || $1 || '%'
|
||||
`
|
||||
|
||||
func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) ([]CompaniesWithWallet, error) {
|
||||
func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) ([]CompaniesDetail, error) {
|
||||
rows, err := q.db.Query(ctx, SearchCompanyByName, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CompaniesWithWallet
|
||||
var items []CompaniesDetail
|
||||
for rows.Next() {
|
||||
var i CompaniesWithWallet
|
||||
var i CompaniesDetail
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
|
|
@ -123,6 +129,9 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text)
|
|||
&i.WalletID,
|
||||
&i.Balance,
|
||||
&i.IsActive,
|
||||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,9 +118,7 @@ SELECT id,
|
|||
status,
|
||||
fetched_at
|
||||
FROM events
|
||||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND start_time < now()
|
||||
WHERE start_time < now()
|
||||
ORDER BY start_time ASC
|
||||
`
|
||||
|
||||
|
|
@ -201,22 +199,32 @@ FROM events
|
|||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND (
|
||||
league_id = $3
|
||||
league_id = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
sport_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
sport_id = $4
|
||||
start_time > $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
ORDER BY start_time ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
LIMIT $6 OFFSET $5
|
||||
`
|
||||
|
||||
type GetPaginatedUpcomingEventsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
type GetPaginatedUpcomingEventsRow struct {
|
||||
|
|
@ -240,10 +248,12 @@ type GetPaginatedUpcomingEventsRow struct {
|
|||
|
||||
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,
|
||||
arg.LastStartTime,
|
||||
arg.FirstStartTime,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -293,15 +303,30 @@ WHERE is_live = false
|
|||
sport_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time < $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
start_time > $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalEventsParams struct {
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalEvents, arg.LeagueID, arg.SportID)
|
||||
row := q.db.QueryRow(ctx, GetTotalEvents,
|
||||
arg.LeagueID,
|
||||
arg.SportID,
|
||||
arg.LastStartTime,
|
||||
arg.FirstStartTime,
|
||||
)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
|
|
|
|||
|
|
@ -146,13 +146,16 @@ type BranchOperation struct {
|
|||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CompaniesWithWallet struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AdminID int64 `json:"admin_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
Balance int64 `json:"balance"`
|
||||
IsActive bool `json:"is_active"`
|
||||
type CompaniesDetail struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AdminID int64 `json:"admin_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
Balance int64 `json:"balance"`
|
||||
IsActive bool `json:"is_active"`
|
||||
AdminFirstName string `json:"admin_first_name"`
|
||||
AdminLastName string `json:"admin_last_name"`
|
||||
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
|
||||
}
|
||||
|
||||
type Company struct {
|
||||
|
|
@ -165,7 +168,6 @@ type Company struct {
|
|||
type CustomerWallet struct {
|
||||
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"`
|
||||
|
|
|
|||
|
|
@ -11,12 +11,52 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const CountUnreadNotifications = `-- name: CountUnreadNotifications :one
|
||||
SELECT count(id)
|
||||
FROM notifications
|
||||
WHERE recipient_id = $1
|
||||
AND is_read = false
|
||||
`
|
||||
|
||||
func (q *Queries) CountUnreadNotifications(ctx context.Context, recipientID int64) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, CountUnreadNotifications, recipientID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const CreateNotification = `-- name: CreateNotification :one
|
||||
INSERT INTO notifications (
|
||||
id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, timestamp, metadata
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
|
||||
) RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
id,
|
||||
recipient_id,
|
||||
type,
|
||||
level,
|
||||
error_severity,
|
||||
reciever,
|
||||
is_read,
|
||||
delivery_status,
|
||||
delivery_channel,
|
||||
payload,
|
||||
priority,
|
||||
timestamp,
|
||||
metadata
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12,
|
||||
$13
|
||||
)
|
||||
RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
`
|
||||
|
||||
type CreateNotificationParams struct {
|
||||
|
|
@ -71,8 +111,58 @@ func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotification
|
|||
return i, err
|
||||
}
|
||||
|
||||
const GetAllNotifications = `-- name: GetAllNotifications :many
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
FROM notifications
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type GetAllNotificationsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetAllNotifications(ctx context.Context, arg GetAllNotificationsParams) ([]Notification, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllNotifications, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Notification
|
||||
for rows.Next() {
|
||||
var i Notification
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.RecipientID,
|
||||
&i.Type,
|
||||
&i.Level,
|
||||
&i.ErrorSeverity,
|
||||
&i.Reciever,
|
||||
&i.IsRead,
|
||||
&i.DeliveryStatus,
|
||||
&i.DeliveryChannel,
|
||||
&i.Payload,
|
||||
&i.Priority,
|
||||
&i.Version,
|
||||
&i.Timestamp,
|
||||
&i.Metadata,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetNotification = `-- name: GetNotification :one
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE id = $1 LIMIT 1
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
FROM notifications
|
||||
WHERE id = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetNotification(ctx context.Context, id string) (Notification, error) {
|
||||
|
|
@ -98,7 +188,12 @@ func (q *Queries) GetNotification(ctx context.Context, id string) (Notification,
|
|||
}
|
||||
|
||||
const ListFailedNotifications = `-- name: ListFailedNotifications :many
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
FROM notifications
|
||||
WHERE delivery_status = 'failed'
|
||||
AND timestamp < NOW() - INTERVAL '1 hour'
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]Notification, error) {
|
||||
|
|
@ -137,7 +232,11 @@ func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]N
|
|||
}
|
||||
|
||||
const ListNotifications = `-- name: ListNotifications :many
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3
|
||||
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
FROM notifications
|
||||
WHERE recipient_id = $1
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
type ListNotificationsParams struct {
|
||||
|
|
@ -182,7 +281,9 @@ func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsPa
|
|||
}
|
||||
|
||||
const ListRecipientIDsByReceiver = `-- name: ListRecipientIDsByReceiver :many
|
||||
SELECT recipient_id FROM notifications WHERE reciever = $1
|
||||
SELECT recipient_id
|
||||
FROM notifications
|
||||
WHERE reciever = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever string) ([]int64, error) {
|
||||
|
|
@ -206,7 +307,12 @@ func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever strin
|
|||
}
|
||||
|
||||
const UpdateNotificationStatus = `-- name: UpdateNotificationStatus :one
|
||||
UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
UPDATE notifications
|
||||
SET delivery_status = $2,
|
||||
is_read = $3,
|
||||
metadata = $4
|
||||
WHERE id = $1
|
||||
RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
|
||||
`
|
||||
|
||||
type UpdateNotificationStatusParams struct {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,61 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetPaginatedPrematchOddsByUpcomingID = `-- name: GetPaginatedPrematchOddsByUpcomingID :many
|
||||
SELECT o.id, 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 $3 OFFSET $2
|
||||
`
|
||||
|
||||
type GetPaginatedPrematchOddsByUpcomingIDParams struct {
|
||||
ID string `json:"id"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, arg GetPaginatedPrematchOddsByUpcomingIDParams) ([]Odd, error) {
|
||||
rows, err := q.db.Query(ctx, GetPaginatedPrematchOddsByUpcomingID, arg.ID, arg.Offset, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Odd
|
||||
for rows.Next() {
|
||||
var i Odd
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&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,
|
||||
|
|
@ -162,21 +217,7 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, er
|
|||
}
|
||||
|
||||
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
|
||||
SELECT o.id, 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
|
||||
|
|
@ -184,43 +225,19 @@ WHERE e.id = $1
|
|||
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)
|
||||
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([]Odd, error) {
|
||||
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetPrematchOddsByUpcomingIDRow
|
||||
var items []Odd
|
||||
for rows.Next() {
|
||||
var i GetPrematchOddsByUpcomingIDRow
|
||||
var i Odd
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.EventID,
|
||||
&i.Fi,
|
||||
&i.MarketType,
|
||||
|
|
|
|||
|
|
@ -182,14 +182,14 @@ wHERE (
|
|||
company_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
LIMIT $3 OFFSET $4
|
||||
LIMIT $4 OFFSET $3
|
||||
`
|
||||
|
||||
type GetAllUsersParams struct {
|
||||
Role string `json:"role"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
type GetAllUsersRow struct {
|
||||
|
|
@ -212,8 +212,8 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
|||
rows, err := q.db.Query(ctx, GetAllUsers,
|
||||
arg.Role,
|
||||
arg.CompanyID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -427,11 +427,27 @@ SELECT id,
|
|||
suspended_at,
|
||||
company_id
|
||||
FROM users
|
||||
WHERE first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%'
|
||||
WHERE (
|
||||
first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%'
|
||||
)
|
||||
AND (
|
||||
role = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
company_id = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type SearchUserByNameOrPhoneParams struct {
|
||||
Column1 pgtype.Text `json:"column_1"`
|
||||
Role pgtype.Text `json:"role"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
type SearchUserByNameOrPhoneRow struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
|
|
@ -448,8 +464,8 @@ type SearchUserByNameOrPhoneRow struct {
|
|||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.Text) ([]SearchUserByNameOrPhoneRow, error) {
|
||||
rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, dollar_1)
|
||||
func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByNameOrPhoneParams) ([]SearchUserByNameOrPhoneRow, error) {
|
||||
rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, arg.Column1, arg.Role, arg.CompanyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -532,31 +548,23 @@ const UpdateUser = `-- name: UpdateUser :exec
|
|||
UPDATE users
|
||||
SET first_name = $1,
|
||||
last_name = $2,
|
||||
email = $3,
|
||||
phone_number = $4,
|
||||
role = $5,
|
||||
updated_at = $6
|
||||
WHERE id = $7
|
||||
suspended = $3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $4
|
||||
`
|
||||
|
||||
type UpdateUserParams struct {
|
||||
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"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Suspended bool `json:"suspended"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateUser,
|
||||
arg.FirstName,
|
||||
arg.LastName,
|
||||
arg.Email,
|
||||
arg.PhoneNumber,
|
||||
arg.Role,
|
||||
arg.UpdatedAt,
|
||||
arg.Suspended,
|
||||
arg.ID,
|
||||
)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -14,33 +14,25 @@ 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
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, customer_id, regular_wallet_id, static_wallet_id, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateCustomerWalletParams struct {
|
||||
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) {
|
||||
row := q.db.QueryRow(ctx, CreateCustomerWallet,
|
||||
arg.CustomerID,
|
||||
arg.CompanyID,
|
||||
arg.RegularWalletID,
|
||||
arg.StaticWalletID,
|
||||
)
|
||||
row := q.db.QueryRow(ctx, CreateCustomerWallet, arg.CustomerID, arg.RegularWalletID, arg.StaticWalletID)
|
||||
var i CustomerWallet
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.CompanyID,
|
||||
&i.RegularWalletID,
|
||||
&i.StaticWalletID,
|
||||
&i.CreatedAt,
|
||||
|
|
@ -190,7 +182,6 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
|||
const GetCustomerWallet = `-- name: GetCustomerWallet :one
|
||||
SELECT cw.id,
|
||||
cw.customer_id,
|
||||
cw.company_id,
|
||||
rw.id AS regular_id,
|
||||
rw.balance AS regular_balance,
|
||||
sw.id AS static_id,
|
||||
|
|
@ -202,18 +193,11 @@ 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
|
||||
`
|
||||
|
||||
type GetCustomerWalletParams struct {
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
}
|
||||
|
||||
type GetCustomerWalletRow struct {
|
||||
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"`
|
||||
|
|
@ -223,13 +207,12 @@ type GetCustomerWalletRow struct {
|
|||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletParams) (GetCustomerWalletRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetCustomerWallet, arg.CustomerID, arg.CompanyID)
|
||||
func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetCustomerWalletRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetCustomerWallet, customerID)
|
||||
var i GetCustomerWalletRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.CompanyID,
|
||||
&i.RegularID,
|
||||
&i.RegularBalance,
|
||||
&i.StaticID,
|
||||
|
|
|
|||
13
go.mod
13
go.mod
|
|
@ -7,24 +7,28 @@ require (
|
|||
github.com/bytedance/sonic v1.13.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
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/stretchr/testify v1.10.0
|
||||
github.com/swaggo/fiber-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/fasthttp/websocket v1.5.8 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
|
|
@ -32,7 +36,7 @@ require (
|
|||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
// github.com/gofiber/contrib/websocket v1.3.4
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
|
|
@ -46,11 +50,10 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -22,8 +22,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
|
||||
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
|
@ -52,13 +50,13 @@ github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
|||
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
|
|
@ -114,8 +112,6 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
|||
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=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
|
|
|
|||
|
|
@ -80,3 +80,83 @@ type CreateBet struct {
|
|||
IsShopBet bool
|
||||
CashoutID string
|
||||
}
|
||||
|
||||
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"`
|
||||
Status OutcomeStatus `json:"status" example:"1"`
|
||||
FullName string `json:"full_name" example:"John"`
|
||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
type RandomBetReq struct {
|
||||
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
|
||||
NumberOfBets int64 `json:"number_of_bets" validate:"required" example:"1"`
|
||||
}
|
||||
|
||||
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 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 []BetOutcome `json:"outcomes"`
|
||||
Amount float32 `json:"amount" example:"100.0"`
|
||||
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||
Status 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 Bet, createdNumber int64) CreateBetRes {
|
||||
return CreateBetRes{
|
||||
ID: bet.ID,
|
||||
Amount: bet.Amount.Float32(),
|
||||
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 GetBet) BetRes {
|
||||
return BetRes{
|
||||
ID: bet.ID,
|
||||
Amount: bet.Amount.Float32(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,27 @@
|
|||
package domain
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ValidInt64 struct {
|
||||
Value int64
|
||||
Valid bool
|
||||
}
|
||||
type ValidInt struct {
|
||||
Value int
|
||||
Valid bool
|
||||
}
|
||||
|
||||
type ValidString struct {
|
||||
Value string
|
||||
Valid bool
|
||||
}
|
||||
type ValidTime struct {
|
||||
Value time.Time
|
||||
Valid bool
|
||||
}
|
||||
type ValidBool struct {
|
||||
Value bool
|
||||
Valid bool
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ type Company struct {
|
|||
}
|
||||
|
||||
type GetCompany struct {
|
||||
ID int64
|
||||
Name string
|
||||
AdminID int64
|
||||
WalletID int64
|
||||
WalletBalance Currency
|
||||
IsWalletActive bool
|
||||
ID int64
|
||||
Name string
|
||||
AdminID int64
|
||||
AdminFirstName string
|
||||
AdminLastName string
|
||||
AdminPhoneNumber string
|
||||
WalletID int64
|
||||
WalletBalance Currency
|
||||
IsWalletActive bool
|
||||
}
|
||||
|
||||
type CreateCompany struct {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ var SupportedLeagues = []int64{
|
|||
10041957, //UEFA Europa League
|
||||
10079560, //UEFA Conference League
|
||||
10047168, // US MLS
|
||||
|
||||
10044469, // Ethiopian Premier League
|
||||
10050282, //UEFA Nations League
|
||||
10040795, //EuroLeague
|
||||
|
||||
10043156, //England FA Cup
|
||||
10042103, //France Cup
|
||||
|
|
@ -26,5 +25,15 @@ var SupportedLeagues = []int64{
|
|||
|
||||
// Basketball
|
||||
173998768, //NBA
|
||||
10041830, //NBA
|
||||
10049984, //WNBA
|
||||
10037165, //German Bundesliga
|
||||
10036608, //Italian Lega 1
|
||||
10040795, //EuroLeague
|
||||
|
||||
// Ice Hockey
|
||||
10037477, //NHL
|
||||
10037447, //AHL
|
||||
10069385, //IIHF World Championship
|
||||
|
||||
}
|
||||
|
|
|
|||
51
internal/domain/oddres.go
Normal file
51
internal/domain/oddres.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package domain
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type BaseNonLiveOddResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []json.RawMessage `json:"results"`
|
||||
}
|
||||
|
||||
type OddsSection struct {
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Sp map[string]OddsMarket `json:"sp"`
|
||||
}
|
||||
|
||||
// The Market ID for the json data can be either string / int which is causing problems when UnMarshalling
|
||||
type OddsMarket struct {
|
||||
ID json.RawMessage `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Odds []json.RawMessage `json:"odds"`
|
||||
Header string `json:"header,omitempty"`
|
||||
Handicap string `json:"handicap,omitempty"`
|
||||
Open int64 `json:"open,omitempty"`
|
||||
}
|
||||
|
||||
type FootballOddsResponse struct {
|
||||
EventID string `json:"event_id"`
|
||||
FI string `json:"FI"`
|
||||
Main OddsSection `json:"main"`
|
||||
AsianLines OddsSection `json:"asian_lines"`
|
||||
Goals OddsSection `json:"goals"`
|
||||
Half OddsSection `json:"half"`
|
||||
}
|
||||
|
||||
type BasketballOddsResponse struct {
|
||||
EventID string `json:"event_id"`
|
||||
FI string `json:"FI"`
|
||||
Main OddsSection `json:"main"`
|
||||
HalfProps OddsSection `json:"half_props"`
|
||||
QuarterProps OddsSection `json:"quarter_props"`
|
||||
TeamProps OddsSection `json:"team_props"`
|
||||
Others []OddsSection `json:"others"`
|
||||
}
|
||||
|
||||
type IceHockeyOddsResponse struct {
|
||||
EventID string `json:"event_id"`
|
||||
FI string `json:"FI"`
|
||||
Main OddsSection `json:"main"`
|
||||
Main2 OddsSection `json:"main_2"`
|
||||
FirstPeriod OddsSection `json:"1st_period"`
|
||||
Others []OddsSection `json:"others"`
|
||||
}
|
||||
|
|
@ -1,185 +1,9 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseResultResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []json.RawMessage `json:"results"`
|
||||
}
|
||||
type FootballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Attacks []string `json:"attacks"`
|
||||
Corners []string `json:"corners"`
|
||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||
Goals []string `json:"goals"`
|
||||
OffTarget []string `json:"off_target"`
|
||||
OnTarget []string `json:"on_target"`
|
||||
Penalties []string `json:"penalties"`
|
||||
PossessionRT []string `json:"possession_rt"`
|
||||
RedCards []string `json:"redcards"`
|
||||
Substitutions []string `json:"substitutions"`
|
||||
YellowCards []string `json:"yellowcards"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
type BasketballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstQuarter Score `json:"1"`
|
||||
SecondQuarter Score `json:"2"`
|
||||
FirstHalf Score `json:"3"`
|
||||
ThirdQuarter Score `json:"4"`
|
||||
FourthQuarter Score `json:"5"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
TwoPoints []string `json:"2points"`
|
||||
ThreePoints []string `json:"3points"`
|
||||
BiggestLead []string `json:"biggest_lead"`
|
||||
Fouls []string `json:"fouls"`
|
||||
FreeThrows []string `json:"free_throws"`
|
||||
FreeThrowRate []string `json:"free_throws_rate"`
|
||||
LeadChanges []string `json:"lead_changes"`
|
||||
MaxpointsInarow []string `json:"maxpoints_inarow"`
|
||||
Possession []string `json:"possession"`
|
||||
SuccessAttempts []string `json:"success_attempts"`
|
||||
TimeSpendInLead []string `json:"timespent_inlead"`
|
||||
Timeuts []string `json:"time_outs"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
AwayManager map[string]string `json:"away_manager"`
|
||||
HomeManager map[string]string `json:"home_manager"`
|
||||
NumberOfPeriods string `json:"numberofperiods"`
|
||||
PeriodLength string `json:"periodlength"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Length string `json:"length"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
type IceHockeyResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstPeriod Score `json:"1"`
|
||||
SecondPeriod Score `json:"2"`
|
||||
ThirdPeriod Score `json:"3"`
|
||||
TotalScore Score `json:"5"`
|
||||
} `json:"scores"`
|
||||
|
||||
Stats struct {
|
||||
Shots []string `json:"shots"`
|
||||
Penalties []string `json:"penalties"`
|
||||
GoalsOnPowerPlay []string `json:"goals_on_power_play"`
|
||||
SSeven []string `json:"s7"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
AwayManager map[string]string `json:"away_manager"`
|
||||
HomeManager map[string]string `json:"home_manager"`
|
||||
NumberOfPeriods string `json:"numberofperiods"`
|
||||
PeriodLength string `json:"periodlength"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Length string `json:"length"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
type Score struct {
|
||||
Home string `json:"home"`
|
||||
Away string `json:"away"`
|
||||
}
|
||||
|
||||
type MarketConfig struct {
|
||||
Sport string
|
||||
MarketCategories map[string]bool
|
||||
|
|
@ -219,4 +43,42 @@ const (
|
|||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
||||
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
||||
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Half Win and Half Given Back
|
||||
)
|
||||
|
||||
func (o *OutcomeStatus) String() string {
|
||||
switch *o {
|
||||
case OUTCOME_STATUS_PENDING:
|
||||
return "PENDING"
|
||||
case OUTCOME_STATUS_WIN:
|
||||
return "WIN"
|
||||
case OUTCOME_STATUS_LOSS:
|
||||
return "LOSS"
|
||||
case OUTCOME_STATUS_VOID:
|
||||
return "VOID"
|
||||
case OUTCOME_STATUS_HALF:
|
||||
return "HALF"
|
||||
case OUTCOME_STATUS_ERROR:
|
||||
return "ERROR"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
type TimeStatus int32
|
||||
|
||||
const (
|
||||
TIME_STATUS_NOT_STARTED TimeStatus = 0
|
||||
TIME_STATUS_IN_PLAY TimeStatus = 1
|
||||
TIME_STATUS_TO_BE_FIXED TimeStatus = 2
|
||||
TIME_STATUS_ENDED TimeStatus = 3
|
||||
TIME_STATUS_POSTPONED TimeStatus = 4
|
||||
TIME_STATUS_CANCELLED TimeStatus = 5
|
||||
TIME_STATUS_WALKOVER TimeStatus = 6
|
||||
TIME_STATUS_INTERRUPTED TimeStatus = 7
|
||||
TIME_STATUS_ABANDONED TimeStatus = 8
|
||||
TIME_STATUS_RETIRED TimeStatus = 9
|
||||
TIME_STATUS_SUSPENDED TimeStatus = 10
|
||||
TIME_STATUS_DECIDED_BY_FA TimeStatus = 11
|
||||
TIME_STATUS_REMOVED TimeStatus = 99
|
||||
)
|
||||
|
|
|
|||
153
internal/domain/resultres.go
Normal file
153
internal/domain/resultres.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type BaseResultResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []json.RawMessage `json:"results"`
|
||||
}
|
||||
|
||||
type League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
}
|
||||
|
||||
type Team struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
}
|
||||
|
||||
type Score struct {
|
||||
Home string `json:"home"`
|
||||
Away string `json:"away"`
|
||||
}
|
||||
|
||||
type FootballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Attacks []string `json:"attacks"`
|
||||
Corners []string `json:"corners"`
|
||||
HalfTimeCorners []string `json:"corner_h"`
|
||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||
Goals []string `json:"goals"`
|
||||
OffTarget []string `json:"off_target"`
|
||||
OnTarget []string `json:"on_target"`
|
||||
Penalties []string `json:"penalties"`
|
||||
PossessionRT []string `json:"possession_rt"`
|
||||
RedCards []string `json:"redcards"`
|
||||
Substitutions []string `json:"substitutions"`
|
||||
YellowCards []string `json:"yellowcards"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
type BasketballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstQuarter Score `json:"1"`
|
||||
SecondQuarter Score `json:"2"`
|
||||
FirstHalf Score `json:"3"`
|
||||
ThirdQuarter Score `json:"4"`
|
||||
FourthQuarter Score `json:"5"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
TwoPoints []string `json:"2points"`
|
||||
ThreePoints []string `json:"3points"`
|
||||
BiggestLead []string `json:"biggest_lead"`
|
||||
Fouls []string `json:"fouls"`
|
||||
FreeThrows []string `json:"free_throws"`
|
||||
FreeThrowRate []string `json:"free_throws_rate"`
|
||||
LeadChanges []string `json:"lead_changes"`
|
||||
MaxpointsInarow []string `json:"maxpoints_inarow"`
|
||||
Possession []string `json:"possession"`
|
||||
SuccessAttempts []string `json:"success_attempts"`
|
||||
TimeSpendInLead []string `json:"timespent_inlead"`
|
||||
TimeOuts []string `json:"time_outs"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
AwayManager map[string]string `json:"away_manager"`
|
||||
HomeManager map[string]string `json:"home_manager"`
|
||||
NumberOfPeriods string `json:"numberofperiods"`
|
||||
PeriodLength string `json:"periodlength"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Length int `json:"length"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
type IceHockeyResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstPeriod Score `json:"1"`
|
||||
SecondPeriod Score `json:"2"`
|
||||
ThirdPeriod Score `json:"3"`
|
||||
TotalScore Score `json:"5"`
|
||||
} `json:"scores"`
|
||||
|
||||
Stats struct {
|
||||
Shots []string `json:"shots"`
|
||||
Penalties []string `json:"penalties"`
|
||||
GoalsOnPowerPlay []string `json:"goals_on_power_play"`
|
||||
SSeven []string `json:"s7"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
AwayManager map[string]string `json:"away_manager"`
|
||||
HomeManager map[string]string `json:"home_manager"`
|
||||
NumberOfPeriods string `json:"numberofperiods"`
|
||||
PeriodLength string `json:"periodlength"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Length int `json:"length"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
|
@ -3,18 +3,28 @@ package domain
|
|||
type FootballMarket int64
|
||||
|
||||
const (
|
||||
FOOTBALL_FULL_TIME_RESULT FootballMarket = 40 //"full_time_result"
|
||||
FOOTBALL_DOUBLE_CHANCE FootballMarket = 10114 //"double_chance"
|
||||
FOOTBALL_GOALS_OVER_UNDER FootballMarket = 981 //"goals_over_under"
|
||||
FOOTBALL_CORRECT_SCORE FootballMarket = 43 //"correct_score"
|
||||
FOOTBALL_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
|
||||
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
|
||||
FOOTBALL_FULL_TIME_RESULT FootballMarket = 40 //"full_time_result"
|
||||
FOOTBALL_DOUBLE_CHANCE FootballMarket = 10114 //"double_chance"
|
||||
FOOTBALL_GOALS_OVER_UNDER FootballMarket = 981 //"goals_over_under"
|
||||
FOOTBALL_CORRECT_SCORE FootballMarket = 43 //"correct_score"
|
||||
FOOTBALL_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
|
||||
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
|
||||
|
||||
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
|
||||
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
|
||||
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
|
||||
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
|
||||
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
|
||||
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
|
||||
|
||||
FOOTBALL_CORNERS FootballMarket = 760 //"corners"
|
||||
FOOTBALL_CORNERS_TWO_WAY FootballMarket = 10235 //"corners_2_way"
|
||||
FOOTBALL_FIRST_HALF_CORNERS FootballMarket = 10539 //"first_half_corners"
|
||||
FOOTBALL_ASIAN_TOTAL_CORNERS FootballMarket = 10164 //"asian_total_corners"
|
||||
FOOTBALL_FIRST_HALF_ASIAN_CORNERS FootballMarket = 10233 //"1st_half_asian_corners"
|
||||
FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN FootballMarket = 10206 //"1st_half_goals_odd_even"
|
||||
FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN FootballMarket = 50433 //"2nd_half_goals_odd_even"
|
||||
|
||||
)
|
||||
|
||||
type BasketBallMarket int64
|
||||
|
|
@ -91,24 +101,63 @@ const (
|
|||
ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY IceHockeyMarket = 170240
|
||||
)
|
||||
|
||||
type AmericanFootballMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
AMERICAN_FOOTBALL_MONEY_LINE AmericanFootballMarket = 170001
|
||||
AMERICAN_FOOTBALL_SPREAD AmericanFootballMarket = 170002
|
||||
AMERICAN_FOOTBALL_TOTAL_POINTS AmericanFootballMarket = 170003
|
||||
)
|
||||
|
||||
type RugbyMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
RUGBY_MONEY_LINE RugbyMarket = 180001
|
||||
RUGBY_SPREAD RugbyMarket = 180002
|
||||
RUGBY_TOTAL_POINTS RugbyMarket = 180003
|
||||
RUGBY_HANDICAP RugbyMarket = 180004
|
||||
RUGBY_FIRST_HALF RugbyMarket = 180005
|
||||
RUGBY_SECOND_HALF RugbyMarket = 180006
|
||||
)
|
||||
|
||||
type BaseballMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
BASEBALL_MONEY_LINE BaseballMarket = 190001
|
||||
BASEBALL_SPREAD BaseballMarket = 190002
|
||||
BASEBALL_TOTAL_RUNS BaseballMarket = 190003
|
||||
BASEBALL_FIRST_INNING BaseballMarket = 190004
|
||||
BASEBALL_FIRST_5_INNINGS BaseballMarket = 190005
|
||||
)
|
||||
|
||||
// TODO: Move this into the database so that it can be modified dynamically
|
||||
|
||||
var SupportedMarkets = map[int64]bool{
|
||||
|
||||
// Football Markets
|
||||
int64(FOOTBALL_FULL_TIME_RESULT): true, //"full_time_result"
|
||||
int64(FOOTBALL_DOUBLE_CHANCE): true, //"double_chance"
|
||||
int64(FOOTBALL_GOALS_OVER_UNDER): true, //"goals_over_under"
|
||||
int64(FOOTBALL_CORRECT_SCORE): true, //"correct_score"
|
||||
int64(FOOTBALL_ASIAN_HANDICAP): true, //"asian_handicap"
|
||||
int64(FOOTBALL_GOAL_LINE): true, //"goal_line"
|
||||
int64(FOOTBALL_HALF_TIME_RESULT): true, //"half_time_result"
|
||||
int64(FOOTBALL_FIRST_HALF_ASIAN_HANDICAP): true, //"1st_half_asian_handicap"
|
||||
int64(FOOTBALL_FIRST_HALF_GOAL_LINE): true, //"1st_half_goal_line"
|
||||
int64(FOOTBALL_FIRST_TEAM_TO_SCORE): true, //"first_team_to_score"
|
||||
int64(FOOTBALL_GOALS_ODD_EVEN): true, //"goals_odd_even"
|
||||
int64(FOOTBALL_DRAW_NO_BET): true, //"draw_no_bet"
|
||||
|
||||
int64(FOOTBALL_FULL_TIME_RESULT): true, //"full_time_result"
|
||||
int64(FOOTBALL_DOUBLE_CHANCE): true, //"double_chance"
|
||||
int64(FOOTBALL_GOALS_OVER_UNDER): true, //"goals_over_under"
|
||||
int64(FOOTBALL_CORRECT_SCORE): true, //"correct_score"
|
||||
int64(FOOTBALL_ASIAN_HANDICAP): true, //"asian_handicap"
|
||||
int64(FOOTBALL_GOAL_LINE): true, //"goal_line"
|
||||
int64(FOOTBALL_HALF_TIME_RESULT): true, //"half_time_result"
|
||||
int64(FOOTBALL_FIRST_HALF_ASIAN_HANDICAP): true, //"1st_half_asian_handicap"
|
||||
int64(FOOTBALL_FIRST_HALF_GOAL_LINE): true, //"1st_half_goal_line"
|
||||
int64(FOOTBALL_FIRST_TEAM_TO_SCORE): true, //"first_team_to_score"
|
||||
int64(FOOTBALL_GOALS_ODD_EVEN): true, //"goals_odd_even"
|
||||
int64(FOOTBALL_DRAW_NO_BET): true, //"draw_no_bet"
|
||||
int64(FOOTBALL_CORNERS): true,
|
||||
int64(FOOTBALL_CORNERS_TWO_WAY): true,
|
||||
int64(FOOTBALL_FIRST_HALF_CORNERS): true,
|
||||
int64(FOOTBALL_ASIAN_TOTAL_CORNERS): true,
|
||||
int64(FOOTBALL_FIRST_HALF_ASIAN_CORNERS): true,
|
||||
int64(FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN): true,
|
||||
int64(FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN): true,
|
||||
|
||||
// Basketball Markets
|
||||
int64(BASKETBALL_GAME_LINES): true,
|
||||
int64(BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
|
||||
|
|
@ -164,4 +213,24 @@ var SupportedMarkets = map[int64]bool{
|
|||
|
||||
int64(ICE_HOCKEY_ALTERNATIVE_PUCK_LINE_TWO_WAY): false,
|
||||
int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false,
|
||||
|
||||
// American Football Markets
|
||||
int64(AMERICAN_FOOTBALL_MONEY_LINE): true,
|
||||
int64(AMERICAN_FOOTBALL_SPREAD): true,
|
||||
int64(AMERICAN_FOOTBALL_TOTAL_POINTS): true,
|
||||
|
||||
// Rugby Markets
|
||||
int64(RUGBY_MONEY_LINE): true,
|
||||
int64(RUGBY_SPREAD): true,
|
||||
int64(RUGBY_TOTAL_POINTS): true,
|
||||
int64(RUGBY_HANDICAP): true,
|
||||
int64(RUGBY_FIRST_HALF): true,
|
||||
int64(RUGBY_SECOND_HALF): true,
|
||||
|
||||
// Baseball Markets
|
||||
int64(BASEBALL_MONEY_LINE): true,
|
||||
int64(BASEBALL_SPREAD): true,
|
||||
int64(BASEBALL_TOTAL_RUNS): true,
|
||||
int64(BASEBALL_FIRST_INNING): true,
|
||||
int64(BASEBALL_FIRST_5_INNINGS): true,
|
||||
}
|
||||
|
|
|
|||
290
internal/domain/sports_result.go
Normal file
290
internal/domain/sports_result.go
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NFLResultResponse represents the structure for NFL game results
|
||||
type NFLResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstQuarter Score `json:"1"`
|
||||
SecondQuarter Score `json:"2"`
|
||||
ThirdQuarter Score `json:"3"`
|
||||
FourthQuarter Score `json:"4"`
|
||||
Overtime Score `json:"5"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
FirstDowns []string `json:"first_downs"`
|
||||
TotalYards []string `json:"total_yards"`
|
||||
PassingYards []string `json:"passing_yards"`
|
||||
RushingYards []string `json:"rushing_yards"`
|
||||
Turnovers []string `json:"turnovers"`
|
||||
TimeOfPossession []string `json:"time_of_possession"`
|
||||
ThirdDownEfficiency []string `json:"third_down_efficiency"`
|
||||
FourthDownEfficiency []string `json:"fourth_down_efficiency"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// RugbyResultResponse represents the structure for Rugby game results
|
||||
type RugbyResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Tries []string `json:"tries"`
|
||||
Conversions []string `json:"conversions"`
|
||||
Penalties []string `json:"penalties"`
|
||||
DropGoals []string `json:"drop_goals"`
|
||||
Possession []string `json:"possession"`
|
||||
Territory []string `json:"territory"`
|
||||
Lineouts []string `json:"lineouts"`
|
||||
Scrums []string `json:"scrums"`
|
||||
PenaltiesConceded []string `json:"penalties_conceded"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// BaseballResultResponse represents the structure for Baseball game results
|
||||
type BaseballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"league"`
|
||||
Home struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"home"`
|
||||
Away struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageID string `json:"image_id"`
|
||||
CC string `json:"cc"`
|
||||
} `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstInning Score `json:"1"`
|
||||
SecondInning Score `json:"2"`
|
||||
ThirdInning Score `json:"3"`
|
||||
FourthInning Score `json:"4"`
|
||||
FifthInning Score `json:"5"`
|
||||
SixthInning Score `json:"6"`
|
||||
SeventhInning Score `json:"7"`
|
||||
EighthInning Score `json:"8"`
|
||||
NinthInning Score `json:"9"`
|
||||
ExtraInnings Score `json:"10"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Hits []string `json:"hits"`
|
||||
Errors []string `json:"errors"`
|
||||
LeftOnBase []string `json:"left_on_base"`
|
||||
Strikeouts []string `json:"strikeouts"`
|
||||
Walks []string `json:"walks"`
|
||||
HomeRuns []string `json:"home_runs"`
|
||||
TotalBases []string `json:"total_bases"`
|
||||
BattingAverage []string `json:"batting_average"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// ParseNFLResult parses NFL result from raw JSON data
|
||||
func ParseNFLResult(data json.RawMessage) (*NFLResultResponse, error) {
|
||||
var result NFLResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ParseRugbyResult parses Rugby result from raw JSON data
|
||||
func ParseRugbyResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
var result RugbyResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ParseRugbyUnionResult parses Rugby Union result from raw JSON data
|
||||
func ParseRugbyUnionResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
return ParseRugbyResult(data)
|
||||
}
|
||||
|
||||
// ParseRugbyLeagueResult parses Rugby League result from raw JSON data
|
||||
func ParseRugbyLeagueResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
return ParseRugbyResult(data)
|
||||
}
|
||||
|
||||
// ParseBaseballResult parses Baseball result from raw JSON data
|
||||
func ParseBaseballResult(data json.RawMessage) (*BaseballResultResponse, error) {
|
||||
var result BaseballResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetNFLWinner determines the winner of an NFL game
|
||||
func GetNFLWinner(result *NFLResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// GetRugbyWinner determines the winner of a Rugby game
|
||||
func GetRugbyWinner(result *RugbyResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// GetBaseballWinner determines the winner of a Baseball game
|
||||
func GetBaseballWinner(result *BaseballResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// FormatNFLScore formats the NFL score in a readable format
|
||||
func FormatNFLScore(result *NFLResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
||||
// FormatRugbyScore formats the Rugby score in a readable format
|
||||
func FormatRugbyScore(result *RugbyResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
||||
// FormatBaseballScore formats the Baseball score in a readable format
|
||||
func FormatBaseballScore(result *BaseballResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
|
@ -62,9 +62,26 @@ type UpdateUserReq struct {
|
|||
FirstName ValidString
|
||||
LastName ValidString
|
||||
Suspended ValidBool
|
||||
CompanyID ValidInt64
|
||||
}
|
||||
|
||||
type UpdateUserReferalCode struct {
|
||||
UserID int64
|
||||
Code string
|
||||
}
|
||||
|
||||
type GetCashier 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 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"`
|
||||
BranchID int64 `json:"branch_id"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ type CustomerWallet struct {
|
|||
RegularID int64
|
||||
StaticID int64
|
||||
CustomerID int64
|
||||
CompanyID int64
|
||||
}
|
||||
type GetCustomerWallet struct {
|
||||
ID int64
|
||||
|
|
@ -28,7 +27,6 @@ type GetCustomerWallet struct {
|
|||
StaticID int64
|
||||
StaticBalance Currency
|
||||
CustomerID int64
|
||||
CompanyID int64
|
||||
RegularUpdatedAt time.Time
|
||||
StaticUpdatedAt time.Time
|
||||
CreatedAt time.Time
|
||||
|
|
@ -56,7 +54,6 @@ type CreateWallet struct {
|
|||
|
||||
type CreateCustomerWallet struct {
|
||||
CustomerID int64
|
||||
CompanyID int64
|
||||
RegularWalletID int64
|
||||
StaticWalletID int64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
// "fmt"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
|
|
@ -225,12 +226,26 @@ func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]do
|
|||
}
|
||||
return result, nil
|
||||
}
|
||||
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
||||
err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||
|
||||
func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) {
|
||||
outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
result = append(result, convertDBBetOutcomes(outcome))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||
Status: int32(status),
|
||||
ID: id,
|
||||
})
|
||||
return err
|
||||
res := convertDBBetOutcomes(update)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
|
||||
|
|
|
|||
|
|
@ -25,14 +25,17 @@ func convertDBCompany(dbCompany dbgen.Company) domain.Company {
|
|||
}
|
||||
}
|
||||
|
||||
func convertDBCompanyWithWallet(dbCompany dbgen.CompaniesWithWallet) domain.GetCompany {
|
||||
func convertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) domain.GetCompany {
|
||||
return domain.GetCompany{
|
||||
ID: dbCompany.ID,
|
||||
Name: dbCompany.Name,
|
||||
AdminID: dbCompany.AdminID,
|
||||
WalletID: dbCompany.WalletID,
|
||||
WalletBalance: domain.Currency(dbCompany.Balance),
|
||||
IsWalletActive: dbCompany.IsActive,
|
||||
ID: dbCompany.ID,
|
||||
Name: dbCompany.Name,
|
||||
AdminID: dbCompany.AdminID,
|
||||
WalletID: dbCompany.WalletID,
|
||||
WalletBalance: domain.Currency(dbCompany.Balance),
|
||||
IsWalletActive: dbCompany.IsActive,
|
||||
AdminFirstName: dbCompany.AdminFirstName,
|
||||
AdminLastName: dbCompany.AdminLastName,
|
||||
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +77,7 @@ func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error
|
|||
|
||||
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
|
||||
for _, dbCompany := range dbCompanies {
|
||||
companies = append(companies, convertDBCompanyWithWallet(dbCompany))
|
||||
companies = append(companies, convertDBCompanyDetails(dbCompany))
|
||||
}
|
||||
|
||||
return companies, nil
|
||||
|
|
@ -92,7 +95,7 @@ func (s *Store) SearchCompanyByName(ctx context.Context, name string) ([]domain.
|
|||
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
|
||||
|
||||
for _, dbCompany := range dbCompanies {
|
||||
companies = append(companies, convertDBCompanyWithWallet(dbCompany))
|
||||
companies = append(companies, convertDBCompanyDetails(dbCompany))
|
||||
}
|
||||
return companies, nil
|
||||
}
|
||||
|
|
@ -103,7 +106,7 @@ func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany
|
|||
if err != nil {
|
||||
return domain.GetCompany{}, err
|
||||
}
|
||||
return convertDBCompanyWithWallet(dbCompany), nil
|
||||
return convertDBCompanyDetails(dbCompany), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) {
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
|
|||
return upcomingEvents, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) {
|
||||
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
|
||||
|
||||
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
|
||||
LeagueID: pgtype.Text{
|
||||
String: leagueID.Value,
|
||||
|
|
@ -127,14 +128,27 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
|||
String: sportID.Value,
|
||||
Valid: sportID.Valid,
|
||||
},
|
||||
Limit: limit,
|
||||
Offset: offset * limit,
|
||||
Limit: pgtype.Int4{
|
||||
Int32: int32(limit.Value),
|
||||
Valid: limit.Valid,
|
||||
},
|
||||
Offset: pgtype.Int4{
|
||||
Int32: int32(offset.Value * limit.Value),
|
||||
Valid: offset.Valid,
|
||||
},
|
||||
FirstStartTime: pgtype.Timestamp{
|
||||
Time: firstStartTime.Value.UTC(),
|
||||
Valid: firstStartTime.Valid,
|
||||
},
|
||||
LastStartTime: pgtype.Timestamp{
|
||||
Time: lastStartTime.Value.UTC(),
|
||||
Valid: lastStartTime.Valid,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
upcomingEvents := make([]domain.UpcomingEvent, len(events))
|
||||
for i, e := range events {
|
||||
upcomingEvents[i] = domain.UpcomingEvent{
|
||||
|
|
@ -162,12 +176,20 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
|||
String: sportID.Value,
|
||||
Valid: sportID.Valid,
|
||||
},
|
||||
FirstStartTime: pgtype.Timestamp{
|
||||
Time: firstStartTime.Value.UTC(),
|
||||
Valid: firstStartTime.Valid,
|
||||
},
|
||||
LastStartTime: pgtype.Timestamp{
|
||||
Time: lastStartTime.Value.UTC(),
|
||||
Valid: lastStartTime.Valid,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
numberOfPages := math.Ceil(float64(totalCount) / float64(limit))
|
||||
numberOfPages := math.Ceil(float64(totalCount) / float64(limit.Value))
|
||||
return upcomingEvents, int64(numberOfPages), nil
|
||||
}
|
||||
func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ type NotificationRepository interface {
|
|||
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
||||
ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error)
|
||||
ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error)
|
||||
CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error)
|
||||
GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
|
|
@ -105,6 +107,24 @@ func (r *Repository) ListNotifications(ctx context.Context, recipientID int64, l
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||
|
||||
dbNotifications, err := r.store.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
||||
Limit: int32(limit),
|
||||
Offset: int32(offset),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications))
|
||||
for _, dbNotif := range dbNotifications {
|
||||
domainNotif := r.mapDBToDomain(&dbNotif)
|
||||
result = append(result, *domainNotif)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) {
|
||||
dbNotifications, err := r.store.queries.ListFailedNotifications(ctx, int32(limit))
|
||||
if err != nil {
|
||||
|
|
@ -177,3 +197,7 @@ func unmarshalPayload(data []byte) (domain.NotificationPayload, error) {
|
|||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
|
||||
return r.store.queries.CountUnreadNotifications(ctx, recipient_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,15 +205,54 @@ func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upco
|
|||
FetchedAt: odds.FetchedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
func (s *Store) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||
odds, err := s.queries.GetPaginatedPrematchOddsByUpcomingID(ctx, dbgen.GetPaginatedPrematchOddsByUpcomingIDParams{
|
||||
ID: upcomingID,
|
||||
Limit: pgtype.Int4{
|
||||
Int32: int32(limit.Value),
|
||||
Valid: limit.Valid,
|
||||
},
|
||||
Offset: pgtype.Int4{
|
||||
Int32: int32(offset.Value),
|
||||
Valid: offset.Valid,
|
||||
},
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
|
||||
params := dbgen.GetPrematchOddsByUpcomingIDParams{
|
||||
ID: upcomingID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, params)
|
||||
return domainOdds, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
||||
|
||||
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, upcomingID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
|
|
@ -90,8 +91,14 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
|
|||
Int64: filter.CompanyID.Value,
|
||||
Valid: filter.CompanyID.Valid,
|
||||
},
|
||||
Limit: int32(filter.PageSize),
|
||||
Offset: int32(filter.Page),
|
||||
Limit: pgtype.Int4{
|
||||
Int32: int32(filter.PageSize.Value),
|
||||
Valid: filter.PageSize.Valid,
|
||||
},
|
||||
Offset: pgtype.Int4{
|
||||
Int32: int32(filter.Page.Value),
|
||||
Valid: filter.Page.Valid,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -123,14 +130,14 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
|
|||
return userList, totalCount, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
|
||||
func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) {
|
||||
users, err := s.queries.GetAllCashiers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userList := make([]domain.User, len(users))
|
||||
userList := make([]domain.GetCashier, len(users))
|
||||
for i, user := range users {
|
||||
userList[i] = domain.User{
|
||||
userList[i] = domain.GetCashier{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
|
|
@ -148,6 +155,28 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
|
|||
return userList, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) {
|
||||
user, err := s.queries.GetCashierByID(ctx, cashierID)
|
||||
if err != nil {
|
||||
return domain.GetCashier{}, err
|
||||
}
|
||||
return domain.GetCashier{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email.String,
|
||||
PhoneNumber: user.PhoneNumber.String,
|
||||
Role: domain.Role(user.Role),
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt.Time,
|
||||
UpdatedAt: user.UpdatedAt.Time,
|
||||
SuspendedAt: user.SuspendedAt.Time,
|
||||
Suspended: user.Suspended,
|
||||
BranchID: user.BranchID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) {
|
||||
users, err := s.queries.GetCashiersByBranch(ctx, branchID)
|
||||
if err != nil {
|
||||
|
|
@ -173,11 +202,28 @@ func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]doma
|
|||
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,
|
||||
})
|
||||
func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error) {
|
||||
|
||||
query := dbgen.SearchUserByNameOrPhoneParams{
|
||||
Column1: pgtype.Text{
|
||||
String: searchString,
|
||||
Valid: true,
|
||||
},
|
||||
CompanyID: pgtype.Int8{
|
||||
Int64: companyID.Value,
|
||||
Valid: companyID.Valid,
|
||||
},
|
||||
}
|
||||
|
||||
if role != nil {
|
||||
|
||||
query.Role = pgtype.Text{
|
||||
String: string(*role),
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
users, err := s.queries.SearchUserByNameOrPhone(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -204,13 +250,12 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string
|
|||
|
||||
func (s *Store) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||
err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
||||
// ID: user.ID,
|
||||
// FirstName: user.FirstName,
|
||||
// LastName: user.LastName,
|
||||
// Email: user.Email,
|
||||
// PhoneNumber: user.PhoneNumber,
|
||||
|
||||
ID: user.UserId,
|
||||
FirstName: user.FirstName.Value,
|
||||
LastName: user.LastName.Value,
|
||||
Suspended: user.Suspended.Value,
|
||||
})
|
||||
fmt.Printf("Updating User %v with values %v", user.UserId, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -230,6 +275,22 @@ func (s *Store) UpdateUserCompany(ctx context.Context, id int64, companyID int64
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||
ID: id,
|
||||
Suspended: status,
|
||||
SuspendedAt: pgtype.Timestamptz{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteUser(ctx context.Context, id int64) error {
|
||||
err := s.queries.DeleteUser(ctx, id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -36,13 +36,11 @@ func convertDBCustomerWallet(customerWallet dbgen.CustomerWallet) domain.Custome
|
|||
RegularID: customerWallet.RegularWalletID,
|
||||
StaticID: customerWallet.StaticWalletID,
|
||||
CustomerID: customerWallet.CustomerID,
|
||||
CompanyID: customerWallet.CompanyID,
|
||||
}
|
||||
}
|
||||
func convertCreateCustomerWallet(customerWallet domain.CreateCustomerWallet) dbgen.CreateCustomerWalletParams {
|
||||
return dbgen.CreateCustomerWalletParams{
|
||||
CustomerID: customerWallet.CustomerID,
|
||||
CompanyID: customerWallet.CompanyID,
|
||||
RegularWalletID: customerWallet.RegularWalletID,
|
||||
StaticWalletID: customerWallet.StaticWalletID,
|
||||
}
|
||||
|
|
@ -56,7 +54,6 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai
|
|||
StaticID: customerWallet.StaticID,
|
||||
StaticBalance: domain.Currency(customerWallet.StaticBalance),
|
||||
CustomerID: customerWallet.CustomerID,
|
||||
CompanyID: customerWallet.CompanyID,
|
||||
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
|
||||
StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time,
|
||||
CreatedAt: customerWallet.CreatedAt.Time,
|
||||
|
|
@ -117,11 +114,8 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) {
|
||||
customerWallet, err := s.queries.GetCustomerWallet(ctx, dbgen.GetCustomerWalletParams{
|
||||
CustomerID: customerID,
|
||||
CompanyID: companyID,
|
||||
})
|
||||
func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
|
||||
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)
|
||||
|
||||
if err != nil {
|
||||
return domain.GetCustomerWallet{}, err
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ type BetStore interface {
|
|||
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)
|
||||
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
|
||||
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, 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
|
||||
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||
DeleteBet(ctx context.Context, id int64) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,21 +3,56 @@ package bet
|
|||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
random "math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
|
||||
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
betStore BetStore
|
||||
betStore BetStore
|
||||
eventSvc event.Service
|
||||
prematchSvc odds.Service
|
||||
walletSvc wallet.Service
|
||||
branchSvc branch.Service
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewService(betStore BetStore) *Service {
|
||||
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Service, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger) *Service {
|
||||
return &Service{
|
||||
betStore: betStore,
|
||||
betStore: betStore,
|
||||
eventSvc: eventSvc,
|
||||
prematchSvc: prematchSvc,
|
||||
walletSvc: walletSvc,
|
||||
branchSvc: branchSvc,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
||||
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
||||
)
|
||||
|
||||
func (s *Service) GenerateCashoutID() (string, error) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
const length int = 13
|
||||
|
|
@ -33,8 +68,402 @@ func (s *Service) GenerateCashoutID() (string, error) {
|
|||
return string(result), nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
|
||||
func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) {
|
||||
// TODO: Change this when you refactor the database code
|
||||
eventIDStr := strconv.FormatInt(eventID, 10)
|
||||
marketIDStr := strconv.FormatInt(marketID, 10)
|
||||
oddIDStr := strconv.FormatInt(oddID, 10)
|
||||
|
||||
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
|
||||
if err != nil {
|
||||
return domain.CreateBetOutcome{}, ErrEventHasBeenRemoved
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
if event.StartTime.Before(currentTime) {
|
||||
return domain.CreateBetOutcome{}, ErrEventHasNotEnded
|
||||
}
|
||||
|
||||
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetOutcome{}, err
|
||||
}
|
||||
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.Printf("Failed to unmarshal raw odd %v", err)
|
||||
continue
|
||||
}
|
||||
if rawOdd.ID == oddIDStr {
|
||||
selectedOdd = rawOdd
|
||||
isOddFound = true
|
||||
}
|
||||
}
|
||||
if !isOddFound {
|
||||
return domain.CreateBetOutcome{}, ErrRawOddInvalid
|
||||
}
|
||||
|
||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||
if err != nil {
|
||||
return domain.CreateBetOutcome{}, err
|
||||
}
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
return domain.CreateBetOutcome{}, err
|
||||
}
|
||||
newOutcome := domain.CreateBetOutcome{
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
SportID: sportID,
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
Odd: float32(parsedOdd),
|
||||
OddName: selectedOdd.Name,
|
||||
OddHeader: selectedOdd.Header,
|
||||
OddHandicap: selectedOdd.Handicap,
|
||||
Expires: event.StartTime,
|
||||
}
|
||||
|
||||
return newOutcome, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) {
|
||||
// You can move the loop over req.Outcomes and all the business logic here.
|
||||
|
||||
if len(req.Outcomes) > 30 {
|
||||
return domain.CreateBetRes{}, ErrOutcomeLimit
|
||||
}
|
||||
|
||||
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
|
||||
var totalOdds float32 = 1
|
||||
|
||||
for _, outcomeReq := range req.Outcomes {
|
||||
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
totalOdds = totalOdds * float32(newOutcome.Odd)
|
||||
outcomes = append(outcomes, newOutcome)
|
||||
}
|
||||
|
||||
// Handle role-specific logic and wallet deduction if needed.
|
||||
var cashoutID string
|
||||
cashoutID, err := s.GenerateCashoutID()
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
newBet := domain.CreateBet{
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
Status: req.Status,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
CashoutID: cashoutID,
|
||||
}
|
||||
switch role {
|
||||
case domain.RoleCashier:
|
||||
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
// Deduct from wallet:
|
||||
// TODO: Make this percentage come from the company
|
||||
var deductedAmount = req.Amount / 10
|
||||
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
newBet.BranchID = domain.ValidInt64{
|
||||
Value: branch.ID,
|
||||
Valid: true,
|
||||
}
|
||||
newBet.UserID = domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: true,
|
||||
}
|
||||
newBet.IsShopBet = true
|
||||
// bet, err = s.betStore.CreateBet(ctx)
|
||||
case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin:
|
||||
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
|
||||
// If a non cashier wants to create a bet, they will need to provide the Branch ID
|
||||
if req.BranchID == nil {
|
||||
return domain.CreateBetRes{}, ErrBranchIDRequired
|
||||
}
|
||||
|
||||
branch, err := s.branchSvc.GetBranchByID(ctx, *req.BranchID)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
// Deduct from wallet:
|
||||
// TODO: Make this percentage come from the company
|
||||
var deductedAmount = req.Amount / 10
|
||||
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
newBet.BranchID = domain.ValidInt64{
|
||||
Value: branch.ID,
|
||||
Valid: true,
|
||||
}
|
||||
newBet.UserID = domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: true,
|
||||
}
|
||||
newBet.IsShopBet = true
|
||||
case domain.RoleCustomer:
|
||||
// Get User Wallet
|
||||
|
||||
wallet, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
userWallet := wallet[0]
|
||||
|
||||
err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount))
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
default:
|
||||
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
||||
}
|
||||
|
||||
bet, err := s.CreateBet(ctx, newBet)
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
// Associate outcomes with the bet.
|
||||
for i := range outcomes {
|
||||
outcomes[i].BetID = bet.ID
|
||||
}
|
||||
rows, err := s.betStore.CreateBetOutcome(ctx, outcomes)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
res := domain.ConvertCreateBet(bet, rows)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
|
||||
|
||||
var newOdds []domain.CreateBetOutcome
|
||||
var totalOdds float32 = 1
|
||||
|
||||
markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to get odds for event", "event id", eventID, "error", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(markets) == 0 {
|
||||
s.logger.Error("empty odds for event", "event id", eventID)
|
||||
return nil, 0, fmt.Errorf("empty odds or event %v", eventID)
|
||||
}
|
||||
|
||||
var selectedMarkets []domain.Odd
|
||||
numMarkets = min(numMarkets, len(markets))
|
||||
for i := 0; i < numMarkets; i++ {
|
||||
randomIndex := random.Intn(len(markets))
|
||||
selectedMarkets = append(selectedMarkets, markets[randomIndex])
|
||||
markets = append(markets[:randomIndex], markets[randomIndex+1:]...)
|
||||
}
|
||||
|
||||
for _, market := range selectedMarkets {
|
||||
|
||||
randomRawOdd := market.RawOdds[random.Intn(len(market.RawOdds))]
|
||||
|
||||
type rawOddType struct {
|
||||
ID string
|
||||
Name string
|
||||
Odds string
|
||||
Header string
|
||||
Handicap string
|
||||
}
|
||||
|
||||
var selectedOdd rawOddType
|
||||
rawBytes, err := json.Marshal(randomRawOdd)
|
||||
err = json.Unmarshal(rawBytes, &selectedOdd)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to unmarshal raw odd %v", err)
|
||||
continue
|
||||
}
|
||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse odd", "error", err)
|
||||
continue
|
||||
}
|
||||
sportID, err := strconv.ParseInt(sportID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sport id", "error", err)
|
||||
continue
|
||||
}
|
||||
eventID, err := strconv.ParseInt(eventID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get event id", "error", err)
|
||||
continue
|
||||
}
|
||||
oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get odd id", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
marketID, err := strconv.ParseInt(market.MarketID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get odd id", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
marketName := market.MarketName
|
||||
|
||||
newOdds = append(newOdds, domain.CreateBetOutcome{
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
SportID: sportID,
|
||||
HomeTeamName: HomeTeam,
|
||||
AwayTeamName: AwayTeam,
|
||||
MarketName: marketName,
|
||||
Odd: float32(parsedOdd),
|
||||
OddName: selectedOdd.Name,
|
||||
OddHeader: selectedOdd.Header,
|
||||
OddHandicap: selectedOdd.Handicap,
|
||||
Expires: StartTime,
|
||||
})
|
||||
|
||||
totalOdds = totalOdds * float32(parsedOdd)
|
||||
|
||||
}
|
||||
|
||||
if len(newOdds) == 0 {
|
||||
s.logger.Error("Bet Outcomes is empty for market", "selectedMarket", selectedMarkets[0].MarketName)
|
||||
return nil, 0, ErrGenerateRandomOutcome
|
||||
}
|
||||
|
||||
return newOdds, totalOdds, nil
|
||||
}
|
||||
|
||||
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidString, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
|
||||
|
||||
// Get a unexpired event id
|
||||
|
||||
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx,
|
||||
domain.ValidInt64{}, domain.ValidInt64{}, leagueID, sportID, firstStartTime, lastStartTime)
|
||||
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
if len(events) == 0 {
|
||||
return domain.CreateBetRes{}, ErrNoEventsAvailable
|
||||
}
|
||||
|
||||
// TODO: Add the option of passing number of created events
|
||||
var selectedUpcomingEvents []domain.UpcomingEvent
|
||||
numEventsPerBet := min(random.Intn(4)+1, len(events)) //Eliminate the option of 0
|
||||
|
||||
for i := 0; i < int(numEventsPerBet); i++ {
|
||||
randomIndex := random.Intn(len(events))
|
||||
selectedUpcomingEvents = append(selectedUpcomingEvents, events[randomIndex])
|
||||
events = append(events[:randomIndex], events[randomIndex+1:]...)
|
||||
|
||||
}
|
||||
|
||||
// s.logger.Info("Generating random bet events", "selectedUpcomingEvents", len(selectedUpcomingEvents))
|
||||
|
||||
// Get market and odds for that
|
||||
var randomOdds []domain.CreateBetOutcome
|
||||
var totalOdds float32 = 1
|
||||
numMarketsPerBet := random.Intn(2) + 1
|
||||
for _, event := range selectedUpcomingEvents {
|
||||
|
||||
newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime, numMarketsPerBet)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
randomOdds = append(randomOdds, newOdds...)
|
||||
totalOdds = totalOdds * total
|
||||
|
||||
}
|
||||
if len(randomOdds) == 0 {
|
||||
s.logger.Error("Failed to generate random any outcomes for all events")
|
||||
return domain.CreateBetRes{}, ErrGenerateRandomOutcome
|
||||
}
|
||||
|
||||
// s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
|
||||
|
||||
var cashoutID string
|
||||
|
||||
cashoutID, err = s.GenerateCashoutID()
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
randomNumber := strconv.FormatInt(int64(random.Intn(100000000000)), 10)
|
||||
newBet := domain.CreateBet{
|
||||
Amount: domain.ToCurrency(123.5),
|
||||
TotalOdds: totalOdds,
|
||||
Status: domain.OUTCOME_STATUS_PENDING,
|
||||
FullName: "test" + randomNumber,
|
||||
PhoneNumber: "0900000000",
|
||||
CashoutID: cashoutID,
|
||||
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
|
||||
UserID: domain.ValidInt64{Valid: true, Value: userID},
|
||||
}
|
||||
|
||||
bet, err := s.CreateBet(ctx, newBet)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
for i := range randomOdds {
|
||||
randomOdds[i].BetID = bet.ID
|
||||
}
|
||||
|
||||
rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds)
|
||||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
res := domain.ConvertCreateBet(bet, rows)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
|
||||
return s.betStore.CreateBet(ctx, bet)
|
||||
}
|
||||
|
||||
|
|
@ -64,8 +493,100 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
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) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) {
|
||||
betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
}
|
||||
status := domain.OUTCOME_STATUS_PENDING
|
||||
|
||||
for _, betOutcome := range betOutcomes {
|
||||
// If any of the bet outcomes are pending return
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||
return domain.OUTCOME_STATUS_PENDING, ErrOutcomesNotCompleted
|
||||
}
|
||||
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_ERROR {
|
||||
return domain.OUTCOME_STATUS_ERROR, nil
|
||||
}
|
||||
|
||||
// The bet status can only be updated if its not lost or error
|
||||
// If all the bet outcomes are a win, then set the bet status to win
|
||||
// If even one of the bet outcomes is a loss then set the bet status to loss
|
||||
// If even one of the bet outcomes is an error, then set the bet status to error
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_PENDING:
|
||||
status = betOutcome.Status
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||
status = domain.OUTCOME_STATUS_HALF
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||
status = domain.OUTCOME_STATUS_WIN
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||
status = domain.OUTCOME_STATUS_VOID
|
||||
} else {
|
||||
status = domain.OUTCOME_STATUS_ERROR
|
||||
}
|
||||
case domain.OUTCOME_STATUS_LOSS:
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else {
|
||||
status = domain.OUTCOME_STATUS_ERROR
|
||||
}
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_VOID ||
|
||||
betOutcome.Status == domain.OUTCOME_STATUS_WIN ||
|
||||
betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||
status = domain.OUTCOME_STATUS_VOID
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
|
||||
} else {
|
||||
status = domain.OUTCOME_STATUS_ERROR
|
||||
}
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_HALF ||
|
||||
betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||
status = domain.OUTCOME_STATUS_HALF
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||
status = domain.OUTCOME_STATUS_LOSS
|
||||
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||
status = domain.OUTCOME_STATUS_VOID
|
||||
} else {
|
||||
status = domain.OUTCOME_STATUS_ERROR
|
||||
}
|
||||
default:
|
||||
// If the status is not pending, win, loss or error, then set the status to error
|
||||
status = domain.OUTCOME_STATUS_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR {
|
||||
// If the status is pending or error, then we don't need to update the bet
|
||||
s.logger.Info("bet not updated", "bet id", betID, "status", status)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("Error when processing bet outcomes")
|
||||
}
|
||||
|
||||
return status, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||
betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
|
||||
if err != nil {
|
||||
return domain.BetOutcome{}, err
|
||||
}
|
||||
|
||||
return betOutcome, err
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) DeleteBet(ctx context.Context, id int64) error {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ type Service interface {
|
|||
FetchUpcomingEvents(ctx context.Context) error
|
||||
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
|
||||
GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
|
||||
GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error)
|
||||
GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error)
|
||||
GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
|
||||
// GetAndStoreMatchResult(ctx context.Context, eventID string) error
|
||||
|
||||
|
|
|
|||
|
|
@ -99,18 +99,17 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||
sportIDs := []int{1, 18}
|
||||
var totalPages int = 1
|
||||
var page int = 0
|
||||
var limit int = 100
|
||||
var count int = 0
|
||||
for _, sportID := range sportIDs {
|
||||
for page != totalPages {
|
||||
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
||||
sportIDs := []int{1, 18, 17}
|
||||
|
||||
for _, sportID := range sportIDs {
|
||||
var totalPages int = 1
|
||||
var page int = 0
|
||||
var limit int = 10
|
||||
var count int = 0
|
||||
for page <= totalPages {
|
||||
page = page + 1
|
||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
||||
log.Printf("📡 Fetching data for event data page %d", page)
|
||||
log.Printf("📡 Fetching data for sport %d event data page %d/%d", sportID, page, min(limit, totalPages))
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
|
||||
|
|
@ -145,9 +144,10 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
} `json:"results"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||
log.Printf("❌ Failed to parse json data")
|
||||
continue
|
||||
}
|
||||
skippedLeague := 0
|
||||
var skippedLeague []string
|
||||
for _, ev := range data.Results {
|
||||
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
||||
// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
|
||||
|
|
@ -163,14 +163,15 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
}
|
||||
|
||||
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
||||
skippedLeague++
|
||||
// fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
|
||||
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
event := domain.UpcomingEvent{
|
||||
ID: ev.ID,
|
||||
SportID: ev.SportID,
|
||||
MatchName: ev.Home.Name,
|
||||
MatchName: "",
|
||||
HomeTeam: ev.Home.Name,
|
||||
AwayTeam: "", // handle nil safely
|
||||
HomeTeamID: ev.Home.ID,
|
||||
|
|
@ -186,13 +187,23 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
if ev.Away != nil {
|
||||
event.AwayTeam = ev.Away.Name
|
||||
event.AwayTeamID = ev.Away.ID
|
||||
event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
|
||||
}
|
||||
|
||||
_ = s.store.SaveUpcomingEvent(ctx, event)
|
||||
err = s.store.SaveUpcomingEvent(ctx, event)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to save upcoming event %s", event.ID)
|
||||
}
|
||||
}
|
||||
totalPages = data.Pager.Total
|
||||
|
||||
if count > limit {
|
||||
log.Printf("⚠️ Skipped leagues %v", len(skippedLeague))
|
||||
// log.Printf("⚠️ Total pages %v", data.Pager.Total)
|
||||
totalPages = data.Pager.Total / data.Pager.PerPage
|
||||
|
||||
if count >= limit {
|
||||
break
|
||||
}
|
||||
if page > totalPages {
|
||||
break
|
||||
}
|
||||
count++
|
||||
|
|
@ -223,8 +234,8 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
|
|||
return s.store.GetExpiredUpcomingEvents(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) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
|
||||
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
|
||||
}
|
||||
|
||||
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type NotificationStore interface {
|
||||
|
|
@ -16,4 +16,6 @@ type NotificationStore interface {
|
|||
SendSMS(ctx context.Context, recipientID int64, message string) error
|
||||
SendEmail(ctx context.Context, recipientID int64, subject, message string) error
|
||||
ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) // New method
|
||||
CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error)
|
||||
GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo repository.NotificationRepository
|
||||
Hub *ws.NotificationHub
|
||||
connections sync.Map
|
||||
notificationCh chan *domain.Notification
|
||||
stopCh chan struct{}
|
||||
|
|
@ -24,9 +26,11 @@ type Service struct {
|
|||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) NotificationStore {
|
||||
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service {
|
||||
hub := ws.NewNotificationHub()
|
||||
svc := &Service{
|
||||
repo: repo,
|
||||
Hub: hub,
|
||||
logger: logger,
|
||||
connections: sync.Map{},
|
||||
notificationCh: make(chan *domain.Notification, 1000),
|
||||
|
|
@ -34,6 +38,7 @@ func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *confi
|
|||
config: cfg,
|
||||
}
|
||||
|
||||
go hub.Run()
|
||||
go svc.startWorker()
|
||||
go svc.startRetryWorker()
|
||||
|
||||
|
|
@ -63,22 +68,48 @@ func (s *Service) SendNotification(ctx context.Context, notification *domain.Not
|
|||
|
||||
notification = created
|
||||
|
||||
if notification.DeliveryChannel == domain.DeliveryChannelInApp {
|
||||
s.Hub.Broadcast <- map[string]interface{}{
|
||||
"type": "CREATED_NOTIFICATION",
|
||||
"recipient_id": notification.RecipientID,
|
||||
"payload": notification,
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case s.notificationCh <- notification:
|
||||
default:
|
||||
s.logger.Error("[NotificationSvc.SendNotification] Notification channel full, dropping notification", "id", notification.ID)
|
||||
s.logger.Warn("[NotificationSvc.SendNotification] Notification channel full, dropping notification", "id", notification.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error {
|
||||
_, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read", "notificationID", notificationID, "recipientID", recipientID, "error", err)
|
||||
return err
|
||||
func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error {
|
||||
for _, notificationID := range notificationIDs {
|
||||
_, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read", "notificationID", notificationID, "recipientID", recipientID, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// count, err := s.repo.CountUnreadNotifications(ctx, recipientID)
|
||||
// if err != nil {
|
||||
// s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// s.Hub.Broadcast <- map[string]interface{}{
|
||||
// "type": "COUNT_NOT_OPENED_NOTIFICATION",
|
||||
// "recipient_id": recipientID,
|
||||
// "payload": map[string]int{
|
||||
// "not_opened_notifications_count": int(count),
|
||||
// },
|
||||
// }
|
||||
|
||||
s.logger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", "notificationID", notificationID, "recipientID", recipientID)
|
||||
}
|
||||
s.logger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", "notificationID", notificationID, "recipientID", recipientID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +123,16 @@ func (s *Service) ListNotifications(ctx context.Context, recipientID int64, limi
|
|||
return notifications, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||
notifications, err := s.repo.GetAllNotifications(ctx, limit, offset)
|
||||
if err != nil {
|
||||
s.logger.Error("[NotificationSvc.ListNotifications] Failed to get all notifications")
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Info("[NotificationSvc.ListNotifications] Successfully retrieved all notifications", "count", len(notifications))
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *websocket.Conn) error {
|
||||
s.addConnection(ctx, recipientID, c)
|
||||
s.logger.Info("[NotificationSvc.ConnectWebSocket] WebSocket connection established", "recipientID", recipientID)
|
||||
|
|
@ -99,7 +140,6 @@ func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *we
|
|||
}
|
||||
|
||||
func (s *Service) DisconnectWebSocket(recipientID int64) {
|
||||
s.connections.Delete(recipientID)
|
||||
if conn, loaded := s.connections.LoadAndDelete(recipientID); loaded {
|
||||
conn.(*websocket.Conn).Close()
|
||||
s.logger.Info("[NotificationSvc.DisconnectWebSocket] Disconnected WebSocket", "recipientID", recipientID)
|
||||
|
|
@ -160,21 +200,26 @@ func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.Notifica
|
|||
func (s *Service) handleNotification(notification *domain.Notification) {
|
||||
ctx := context.Background()
|
||||
|
||||
if conn, ok := s.connections.Load(notification.RecipientID); ok {
|
||||
data, err := notification.ToJSON()
|
||||
switch notification.DeliveryChannel {
|
||||
case domain.DeliveryChannelSMS:
|
||||
err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message)
|
||||
if err != nil {
|
||||
s.logger.Error("[NotificationSvc.HandleNotification] Failed to serialize notification", "id", notification.ID, "error", err)
|
||||
return
|
||||
}
|
||||
if err := conn.(*websocket.Conn).WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
s.logger.Error("[NotificationSvc.HandleNotification] Failed to send WebSocket message", "id", notification.ID, "error", err)
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
} else {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("[NotificationSvc.HandleNotification] No WebSocket connection for recipient", "recipientID", notification.RecipientID)
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
case domain.DeliveryChannelEmail:
|
||||
err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message)
|
||||
if err != nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
} else {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
}
|
||||
default:
|
||||
if notification.DeliveryChannel != domain.DeliveryChannelInApp {
|
||||
s.logger.Warn("[NotificationSvc.HandleNotification] Unsupported delivery channel", "channel", notification.DeliveryChannel)
|
||||
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
|
|
@ -210,13 +255,17 @@ func (s *Service) retryFailedNotifications() {
|
|||
go func(notification *domain.Notification) {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
if conn, ok := s.connections.Load(notification.RecipientID); ok {
|
||||
data, err := notification.ToJSON()
|
||||
if err != nil {
|
||||
s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to serialize notification for retry", "id", notification.ID, "error", err)
|
||||
continue
|
||||
if notification.DeliveryChannel == domain.DeliveryChannelSMS {
|
||||
if err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", "id", notification.ID, "error", err)
|
||||
}
|
||||
s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID)
|
||||
return
|
||||
}
|
||||
if err := conn.(*websocket.Conn).WriteMessage(websocket.TextMessage, data); err == nil {
|
||||
} else if notification.DeliveryChannel == domain.DeliveryChannelEmail {
|
||||
if err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", "id", notification.ID, "error", err)
|
||||
|
|
@ -230,3 +279,7 @@ func (s *Service) retryFailedNotifications() {
|
|||
}(notification)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
|
||||
return s.repo.CountUnreadNotifications(ctx, recipient_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
type Service interface {
|
||||
FetchNonLiveOdds(ctx context.Context) error
|
||||
GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error)
|
||||
GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error)
|
||||
GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error)
|
||||
GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error)
|
||||
GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,26 +3,37 @@ package odds
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
token string
|
||||
store *repository.Store
|
||||
store *repository.Store
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(token string, store *repository.Store) *ServiceImpl {
|
||||
return &ServiceImpl{token: token, store: store}
|
||||
func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *ServiceImpl {
|
||||
return &ServiceImpl{
|
||||
store: store,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is only getting the main odds, this must be fixed
|
||||
// TODO Add the optimization to get 10 events at the same time
|
||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -30,86 +41,252 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, event := range eventIDs {
|
||||
// time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
||||
var errs []error
|
||||
|
||||
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)
|
||||
for index, event := range eventIDs {
|
||||
|
||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to fetch prematch odds for event %s: %v", eventID, err)
|
||||
s.logger.Error("Failed to parse event id")
|
||||
return err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)
|
||||
|
||||
log.Printf("📡 Fetching prematch odds for event ID: %d (%d/%d) ", eventID, index, len(eventIDs))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to create request for event %d: %v", eventID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to fetch prematch odds for event %d: %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"`
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to read response body for event %d: %v", eventID, err)
|
||||
continue
|
||||
}
|
||||
var oddsData domain.BaseNonLiveOddResponse
|
||||
|
||||
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
||||
log.Printf("❌ Invalid prematch data for event %s", eventID)
|
||||
log.Printf("❌ Invalid prematch data for event %d", eventID)
|
||||
continue
|
||||
}
|
||||
|
||||
result := oddsData.Results[0]
|
||||
finalID := result.EventID
|
||||
if finalID == "" {
|
||||
finalID = result.FI
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
|
||||
switch sportID {
|
||||
case domain.FOOTBALL:
|
||||
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
|
||||
s.logger.Error("Error while inserting football odd")
|
||||
errs = append(errs, err)
|
||||
}
|
||||
case domain.BASKETBALL:
|
||||
if err := s.parseBasketball(ctx, oddsData.Results[0]); err != nil {
|
||||
s.logger.Error("Error while inserting basketball odd")
|
||||
errs = append(errs, err)
|
||||
}
|
||||
case domain.ICE_HOCKEY:
|
||||
if err := s.parseIceHockey(ctx, oddsData.Results[0]); err != nil {
|
||||
s.logger.Error("Error while inserting ice hockey odd")
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if finalID == "" {
|
||||
log.Printf("⚠️ Skipping event %s with no valid ID", eventID)
|
||||
continue
|
||||
}
|
||||
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
|
||||
|
||||
// result := oddsData.Results[0]
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) {
|
||||
func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error {
|
||||
var footballRes domain.FootballOddsResponse
|
||||
if err := json.Unmarshal(res, &footballRes); err != nil {
|
||||
s.logger.Error("Failed to unmarshal football result", "error", err)
|
||||
return err
|
||||
}
|
||||
if footballRes.EventID == "" && footballRes.FI == "" {
|
||||
s.logger.Error("Skipping football result with no valid Event ID", "eventID", footballRes.EventID, "fi", footballRes.FI)
|
||||
return fmt.Errorf("Skipping football result with no valid Event ID Event ID %v", footballRes.EventID)
|
||||
}
|
||||
sections := map[string]domain.OddsSection{
|
||||
"main": footballRes.Main,
|
||||
"asian_lines": footballRes.AsianLines,
|
||||
"goals": footballRes.Goals,
|
||||
"half": footballRes.Half,
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for oddCategory, section := range sections {
|
||||
if err := s.storeSection(ctx, footballRes.EventID, footballRes.FI, oddCategory, section); err != nil {
|
||||
s.logger.Error("Error storing football section", "eventID", footballRes.FI, "odd", oddCategory)
|
||||
log.Printf("⚠️ Error when storing football %v", err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) parseBasketball(ctx context.Context, res json.RawMessage) error {
|
||||
var basketballRes domain.BasketballOddsResponse
|
||||
if err := json.Unmarshal(res, &basketballRes); err != nil {
|
||||
s.logger.Error("Failed to unmarshal basketball result", "error", err)
|
||||
return err
|
||||
}
|
||||
if basketballRes.EventID == "" && basketballRes.FI == "" {
|
||||
s.logger.Error("Skipping basketball result with no valid Event ID")
|
||||
return fmt.Errorf("Skipping basketball result with no valid Event ID")
|
||||
}
|
||||
sections := map[string]domain.OddsSection{
|
||||
"main": basketballRes.Main,
|
||||
"half_props": basketballRes.HalfProps,
|
||||
"quarter_props": basketballRes.QuarterProps,
|
||||
"team_props": basketballRes.TeamProps,
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for oddCategory, section := range sections {
|
||||
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, oddCategory, section); err != nil {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, section := range basketballRes.Others {
|
||||
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, "others", section); err != nil {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (s *ServiceImpl) parseIceHockey(ctx context.Context, res json.RawMessage) error {
|
||||
var iceHockeyRes domain.IceHockeyOddsResponse
|
||||
if err := json.Unmarshal(res, &iceHockeyRes); err != nil {
|
||||
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
||||
return err
|
||||
}
|
||||
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
return fmt.Errorf("Skipping result with no valid Event ID")
|
||||
}
|
||||
sections := map[string]domain.OddsSection{
|
||||
"main": iceHockeyRes.Main,
|
||||
"main_2": iceHockeyRes.Main2,
|
||||
"1st_period": iceHockeyRes.FirstPeriod,
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for oddCategory, section := range sections {
|
||||
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, oddCategory, section); err != nil {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, section := range iceHockeyRes.Others {
|
||||
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, "others", section); err != nil {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error {
|
||||
if len(section.Sp) == 0 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64)
|
||||
updatedAt := time.Unix(updatedAtUnix, 0)
|
||||
|
||||
var errs []error
|
||||
for marketType, market := range section.Sp {
|
||||
if len(market.Odds) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the market id is a string
|
||||
var marketIDstr string
|
||||
err := json.Unmarshal(market.ID, &marketIDstr)
|
||||
if err != nil {
|
||||
// check if its int
|
||||
var marketIDint int
|
||||
err := json.Unmarshal(market.ID, &marketIDint)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
marketIDint, err := strconv.ParseInt(marketIDstr, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
isSupported, ok := domain.SupportedMarkets[marketIDint]
|
||||
|
||||
if !ok || !isSupported {
|
||||
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
marketRecord := domain.Market{
|
||||
EventID: eventID,
|
||||
FI: fi,
|
||||
MarketCategory: sectionName,
|
||||
MarketType: marketType,
|
||||
MarketName: market.Name,
|
||||
MarketID: market.ID.String(),
|
||||
MarketID: marketIDstr,
|
||||
UpdatedAt: updatedAt,
|
||||
Odds: market.Odds,
|
||||
}
|
||||
|
||||
_ = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
||||
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to save market", "market_id", market.ID, "error", err)
|
||||
errs = append(errs, fmt.Errorf("market %s: %w", market.ID, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OddsMarket struct {
|
||||
ID json.Number `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"`
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
||||
|
|
@ -129,6 +306,10 @@ func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string,
|
|||
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)
|
||||
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
||||
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID)
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
)
|
||||
|
||||
// Football evaluations
|
||||
|
||||
// Full Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the full 90 minutes of play.
|
||||
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddName {
|
||||
case "1": // Home win
|
||||
|
|
@ -27,15 +29,16 @@ func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Over/Under betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be over or under a specified number.
|
||||
func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalGoals := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
|
|
@ -53,9 +56,10 @@ func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// Correct Score betting is a type of bet where the bettor predicts the exact final score of a match.
|
||||
func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
|
||||
if outcome.OddName == expectedScore {
|
||||
|
|
@ -64,6 +68,8 @@ func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away in
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Half Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the first half.
|
||||
// This is the same as the full time result but only for the first half of the game
|
||||
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
return evaluateFullTimeResult(outcome, score)
|
||||
}
|
||||
|
|
@ -71,43 +77,94 @@ func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
// This is a multiple outcome checker for the asian handicap and other kinds of bets
|
||||
// The only outcome that are allowed are "Both Bets win", "Both Bets Lose", "Half Win and Half Void"
|
||||
func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.OutcomeStatus) (domain.OutcomeStatus, error) {
|
||||
if secondOutcome == domain.OUTCOME_STATUS_PENDING {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("cannot check pending outcome")
|
||||
}
|
||||
|
||||
if outcome == domain.OUTCOME_STATUS_ERROR || secondOutcome == domain.OUTCOME_STATUS_ERROR {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("❌ mutli outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
}
|
||||
|
||||
// fmt.Printf("| Multi Outcome | %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
|
||||
switch outcome {
|
||||
case domain.OUTCOME_STATUS_PENDING:
|
||||
return secondOutcome, nil
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_WIN {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||
return domain.OUTCOME_STATUS_HALF, nil
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||
}
|
||||
case domain.OUTCOME_STATUS_LOSS:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||
if secondOutcome == domain.OUTCOME_STATUS_LOSS ||
|
||||
secondOutcome == domain.OUTCOME_STATUS_WIN ||
|
||||
secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||
return domain.OUTCOME_STATUS_HALF, nil
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||
}
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||
return domain.OUTCOME_STATUS_HALF, nil
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID || secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||
}
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
} else {
|
||||
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||
}
|
||||
}
|
||||
|
||||
// Asian Handicap betting is a type of betting that eliminates the possibility of a draw by giving one team a virtual advantage or disadvantage.
|
||||
// When the handicap has two values like "+0.5, +1.0" or "-0.5, -1.0", then it a multi outcome bet
|
||||
// .
|
||||
//
|
||||
// {
|
||||
// "id": "548319135",
|
||||
// "odds": "1.750",
|
||||
// "header": "1",
|
||||
// "handicap": "+0.5, +1.0"
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// "id": "548319139",
|
||||
// "odds": "1.950",
|
||||
// "header": "2",
|
||||
// "handicap": "-0.5, -1.0"
|
||||
// }
|
||||
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicapList := strings.Split(outcome.OddHandicap, ",")
|
||||
newOutcome := domain.OUTCOME_STATUS_PENDING
|
||||
for _, handicapStr := range handicapList {
|
||||
handicapStr = strings.TrimSpace(handicapStr)
|
||||
handicap, err := strconv.ParseFloat(handicapStr, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
|
@ -116,49 +173,123 @@ func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away i
|
|||
} else if outcome.OddHeader == "2" { // Away team
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
continue
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
continue
|
||||
} else if adjustedHomeScore == adjustedAwayScore {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
return newOutcome, nil
|
||||
}
|
||||
|
||||
// Goal Line betting, also known as Over/Under betting,
|
||||
// involves predicting the total number of goals scored in a match, regardless of which team wins.
|
||||
//
|
||||
// {
|
||||
// "id": "548319141",
|
||||
// "odds": "1.800",
|
||||
// "header": "Over",
|
||||
// "name": "1.5, 2.0"
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// "id": "548319146",
|
||||
// "odds": "1.900",
|
||||
// "header": "Under",
|
||||
// "name": "1.5, 2.0"
|
||||
// }
|
||||
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
return evaluateGoalsOverUnder(outcome, score)
|
||||
|
||||
totalGoals := float64(score.Home + score.Away)
|
||||
thresholdList := strings.Split(outcome.OddName, ",")
|
||||
|
||||
newOutcome := domain.OUTCOME_STATUS_PENDING
|
||||
for _, thresholdStr := range thresholdList {
|
||||
thresholdStr = strings.TrimSpace(thresholdStr)
|
||||
threshold, err := strconv.ParseFloat(thresholdStr, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: '%s', %v", thresholdStr, err)
|
||||
}
|
||||
|
||||
oddHeader := strings.TrimSpace(outcome.OddHeader)
|
||||
if oddHeader == "Over" {
|
||||
if totalGoals > threshold {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
} else if totalGoals == threshold {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||
if err != nil {
|
||||
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
} else if oddHeader == "Under" {
|
||||
if totalGoals < threshold {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
} else if totalGoals == threshold {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||
if err != nil {
|
||||
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: '%s'", oddHeader)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return newOutcome, nil
|
||||
}
|
||||
|
||||
// First Team To Score betting is a type of bet where the bettor predicts which team will score first in a match.
|
||||
// We can get this from the "events" field on the result json
|
||||
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||
for _, event := range events {
|
||||
if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") {
|
||||
|
|
@ -173,6 +304,7 @@ func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]str
|
|||
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
||||
}
|
||||
|
||||
// Goals Odd/Even betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be odd or even.
|
||||
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalGoals := score.Home + score.Away
|
||||
isOdd := totalGoals%2 == 1
|
||||
|
|
@ -184,32 +316,70 @@ func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away in
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
func evaluateTeamOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if outcome.OddHandicap == "Odd" {
|
||||
if score.Home%2 == 1 {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHandicap == "Even" {
|
||||
if score.Home%2 == 0 {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
case "2":
|
||||
if outcome.OddHandicap == "Odd" {
|
||||
if score.Away%2 == 1 {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHandicap == "Even" {
|
||||
if score.Away%2 == 0 {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Double Chance betting is a type of bet where the bettor predicts two of the three possible outcomes of a match.
|
||||
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
isHomeWin := score.Home > score.Away
|
||||
isDraw := score.Home == score.Away
|
||||
isAwayWin := score.Away > score.Home
|
||||
|
||||
switch outcome.OddName {
|
||||
case "1 or Draw", (outcome.HomeTeamName + " or " + "Draw"):
|
||||
case "1 or Draw", (outcome.HomeTeamName + " or " + "Draw"), ("Draw" + " or " + outcome.HomeTeamName):
|
||||
if isHomeWin || isDraw {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "Draw or 2", ("Draw" + " or " + outcome.AwayTeamName):
|
||||
case "Draw or 2", ("Draw" + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + "Draw"):
|
||||
if isDraw || isAwayWin {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "1 or 2", (outcome.HomeTeamName + " or " + outcome.AwayTeamName):
|
||||
case "1 or 2", (outcome.HomeTeamName + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + outcome.HomeTeamName):
|
||||
if isHomeWin || isAwayWin {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw No Bet betting is a type of bet where the bettor predicts the outcome of a match, but if the match ends in a draw, the bet is voided.
|
||||
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
|
|
@ -222,8 +392,37 @@ func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// basketball evaluations
|
||||
func evaluateCorners(outcome domain.BetOutcome, corners struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
totalCorners := corners.Home + corners.Away
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 10)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
switch outcome.OddHeader {
|
||||
case "Over":
|
||||
if totalCorners > int(threshold) {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "Under":
|
||||
if totalCorners < int(threshold) {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "Exactly":
|
||||
if totalCorners == int(threshold) {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Basketball evaluations
|
||||
|
||||
// Game Lines is an aggregate of money line, spread and total betting markets in one
|
||||
func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddName {
|
||||
case "Money Line":
|
||||
|
|
@ -235,10 +434,11 @@ func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
case "Total":
|
||||
return evaluateTotalOverUnder(outcome, score)
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Money Line betting is a type of bet where the bettor predicts the outcome of a match without any point spread.
|
||||
func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
|
|
@ -258,21 +458,22 @@ func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Total Over/Under betting is a type of bet where the bettor predicts whether the total number of points scored in a match will be over or under a specified number.
|
||||
func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
if len(overUnderStr) != 2 {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||
|
|
@ -294,26 +495,28 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// Result and Total betting is a type of bet where the bettor predicts
|
||||
// the outcome of a match and whether the total number of points scored will be over or under a specified number.
|
||||
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
if len(overUnderStr) != 2 {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
overUnder := overUnderStr[0]
|
||||
|
||||
if overUnder != "Over" && overUnder != "Under" {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||
}
|
||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||
|
|
@ -321,6 +524,10 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home < score.Away {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
if overUnder == "Over" && totalScore > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if overUnder == "Under" && totalScore < threshold {
|
||||
|
|
@ -328,6 +535,9 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away < score.Home {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
if overUnder == "Over" && totalScore > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if overUnder == "Under" && totalScore < threshold {
|
||||
|
|
@ -336,27 +546,29 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match
|
||||
// is over or under a specified number.
|
||||
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
if len(overUnderStr) != 2 {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
overUnder := overUnderStr[0]
|
||||
|
||||
if overUnder != "Over" && overUnder != "Under" {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||
}
|
||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||
|
|
@ -380,11 +592,12 @@ func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate Result and Both Teams To Score X Points
|
||||
// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
|
||||
// and also the result fo the match
|
||||
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
// The name parameter will hold value "name": "{team_name} and {Yes | No}"
|
||||
|
|
@ -400,14 +613,14 @@ func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
} else if scoreCheckSplit == "No" {
|
||||
isScorePoints = false
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
teamName := strings.TrimSpace(strings.Join(oddNameSplit[:len(oddNameSplit)-2], ""))
|
||||
|
||||
threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
switch teamName {
|
||||
|
|
@ -428,18 +641,18 @@ func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("team name error: %s", teamName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("team name error: %s", teamName)
|
||||
}
|
||||
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
|
||||
}
|
||||
|
||||
// Both Teams To Score X Points
|
||||
// Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points.
|
||||
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
switch outcome.OddHeader {
|
||||
|
|
@ -453,12 +666,13 @@ func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (d
|
|||
}
|
||||
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw.
|
||||
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddName {
|
||||
case "1": // Home win
|
||||
|
|
@ -477,23 +691,24 @@ func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away i
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
}
|
||||
|
||||
// Double Result betting is a type of bet where the bettor predicts the outcome of a match at both half-time and full-time.
|
||||
func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
halfWins := strings.Split(outcome.OddName, "-")
|
||||
if len(halfWins) != 2 {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||
}
|
||||
firstHalfWinner := strings.TrimSpace(halfWins[0])
|
||||
secondHalfWinner := strings.TrimSpace(halfWins[1])
|
||||
|
||||
if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
}
|
||||
if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
@ -517,6 +732,7 @@ func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
|
||||
// Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score.
|
||||
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
firstHalfTotal := firstScore.Home + firstScore.Away
|
||||
secondHalfTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -534,11 +750,12 @@ func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Ho
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score.
|
||||
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
firstQuarterTotal := firstScore.Home + firstScore.Away
|
||||
secondQuarterTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -567,18 +784,44 @@ func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Team With Highest Scoring Quarter betting is a type of bet where the bettor predicts which team will have the highest score in a specific quarter.
|
||||
func evaluateTeamWithHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
homeTeamHighestQuarter := max(firstScore.Home, secondScore.Home, thirdScore.Home, fourthScore.Home)
|
||||
awayTeamHighestQuarter := max(firstScore.Away, secondScore.Away, thirdScore.Away, fourthScore.Away)
|
||||
|
||||
switch outcome.OddName {
|
||||
case "1":
|
||||
if homeTeamHighestQuarter > awayTeamHighestQuarter {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
case "2":
|
||||
if awayTeamHighestQuarter > homeTeamHighestQuarter {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
case "Tie":
|
||||
if homeTeamHighestQuarter == awayTeamHighestQuarter {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Handicap and Total betting is a combination of spread betting and total points betting
|
||||
// where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number.
|
||||
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
nameSplit := strings.Split(outcome.OddName, " ")
|
||||
// Evaluate from bottom to get the threshold and find out if its over or under
|
||||
threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing threshold: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing threshold: %s", outcome.OddName)
|
||||
}
|
||||
total := float64(score.Home + score.Away)
|
||||
overUnder := nameSplit[len(nameSplit)-2]
|
||||
|
|
@ -591,12 +834,12 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over and under: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over and under: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
handicap, err := strconv.ParseFloat(nameSplit[len(nameSplit)-4], 10)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing handicap: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing handicap: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
|
||||
|
|
@ -618,21 +861,22 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Winning Margin betting is a type of bet where the bettor predicts the margin of victory in a match.
|
||||
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
|
||||
marginSplit := strings.Split(outcome.OddName, "")
|
||||
if len(marginSplit) < 1 {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
margin, err := strconv.ParseInt(marginSplit[0], 10, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
isGtr := false
|
||||
|
|
@ -656,9 +900,10 @@ func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away i
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score.
|
||||
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
firstPeriodTotal := firstScore.Home + firstScore.Away
|
||||
secondPeriodTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -682,11 +927,12 @@ func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
// Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time.
|
||||
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalScore := struct{ Home, Away int }{0, 0}
|
||||
for _, score := range scores {
|
||||
|
|
@ -706,6 +952,37 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
// evaluateRugbyOutcome evaluates the outcome of a Rugby bet
|
||||
func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
|
||||
finalScore := parseSS(result.SS)
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
return evaluateRugbyMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
return evaluateRugbySpread(outcome, finalScore)
|
||||
case "Total Points":
|
||||
return evaluateRugbyTotalPoints(outcome, finalScore)
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballOutcome evaluates the outcome of a Baseball bet
|
||||
func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
|
||||
finalScore := parseSS(result.SS)
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
return evaluateBaseballMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
return evaluateBaseballSpread(outcome, finalScore)
|
||||
case "Total Runs":
|
||||
return evaluateBaseballTotalRuns(outcome, finalScore)
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
30
internal/services/result/football_test.go
Normal file
30
internal/services/result/football_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func TestEvaluateFullTimeResult(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
outcome domain.BetOutcome
|
||||
score struct{ Home, Away int }
|
||||
expected domain.OutcomeStatus
|
||||
}{
|
||||
{"Home win", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
|
||||
{"Away win", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
|
||||
{"Draw", domain.BetOutcome{OddName: "Draw"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_WIN},
|
||||
{"Home selected, but Draw", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_LOSS},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateFullTimeResult(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
|
@ -20,19 +21,22 @@ type Service struct {
|
|||
config *config.Config
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
betSvc bet.Service
|
||||
}
|
||||
|
||||
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger) *Service {
|
||||
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service) *Service {
|
||||
return &Service{
|
||||
repo: repo,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
betSvc: betSvc,
|
||||
}
|
||||
}
|
||||
|
||||
type ResultCheck struct {
|
||||
}
|
||||
var (
|
||||
ErrEventIsNotActive = fmt.Errorf("Event has been cancelled or postponed")
|
||||
)
|
||||
|
||||
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||
// TODO: Optimize this because there could be many bet outcomes for the same odd
|
||||
|
|
@ -42,9 +46,10 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
|||
s.logger.Error("Failed to fetch events")
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Expired Events: %d \n", len(events))
|
||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
||||
removed := 0
|
||||
for i, event := range events {
|
||||
|
||||
for _, event := range events {
|
||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse event id")
|
||||
|
|
@ -56,48 +61,102 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
if len(outcomes) == 0 {
|
||||
fmt.Printf("🕛 No bets have been placed on event %v (%d/%d) \n", event.ID, i+1, len(events))
|
||||
} else {
|
||||
fmt.Printf("✅ %d bets have been placed on event %v (%d/%d) \n", len(outcomes), event.ID, i+1, len(events))
|
||||
}
|
||||
|
||||
isDeleted := true
|
||||
for j, outcome := range outcomes {
|
||||
fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||
outcome.MarketName,
|
||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||
j+1, len(outcomes))
|
||||
|
||||
if outcome.Expires.After(time.Now()) {
|
||||
isDeleted = false
|
||||
s.logger.Warn("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
|
||||
isDeleted = false
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: optimize this because the result is being fetched for each outcome which will have the same event id but different market id
|
||||
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, sportID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "error", err)
|
||||
if err == ErrEventIsNotActive {
|
||||
s.logger.Warn("Event is not active", "event_id", outcome.EventID, "error", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("❌ failed to parse 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||
outcome.MarketName,
|
||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||
j+1, len(outcomes))
|
||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "market_id", outcome.MarketID, "market", outcome.MarketName, "error", err)
|
||||
isDeleted = false
|
||||
continue
|
||||
}
|
||||
|
||||
// _, err = s.repo.CreateResult(ctx, domain.CreateResult{
|
||||
// BetOutcomeID: outcome.ID,
|
||||
// EventID: outcome.EventID,
|
||||
// OddID: outcome.OddID,
|
||||
// MarketID: outcome.MarketID,
|
||||
// Status: result.Status,
|
||||
// Score: result.Score,
|
||||
// })
|
||||
// if err != nil {
|
||||
// s.logger.Error("Failed to store result", "bet_outcome_id", outcome.ID, "error", err)
|
||||
// continue
|
||||
// }
|
||||
|
||||
err = s.repo.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||
if err != nil {
|
||||
isDeleted = false
|
||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||
fmt.Printf("❌ Error while updating 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||
outcome.MarketName,
|
||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||
j+1, len(outcomes))
|
||||
|
||||
s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
||||
isDeleted = false
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Successfully updated 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||
outcome.MarketName,
|
||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||
j+1, len(outcomes))
|
||||
|
||||
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
||||
if err != nil {
|
||||
if err != bet.ErrOutcomesNotCompleted {
|
||||
s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
||||
}
|
||||
isDeleted = false
|
||||
continue
|
||||
}
|
||||
fmt.Printf("🧾 Updating bet %v - event %v (%d/%d) to %v\n", outcome.BetID, event.ID, j+1, len(outcomes), status.String())
|
||||
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
||||
isDeleted = false
|
||||
continue
|
||||
}
|
||||
fmt.Printf("✅ Successfully updated 🎫 Bet %v - event %v(%v) (%d/%d) \n",
|
||||
outcome.BetID,
|
||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||
j+1, len(outcomes))
|
||||
|
||||
}
|
||||
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||
return err
|
||||
if isDeleted {
|
||||
removed += 1
|
||||
fmt.Printf("⚠️ Removing Event %v \n", event.ID)
|
||||
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
fmt.Printf("🗑️ Removed Events: %d \n", removed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -202,29 +261,51 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
|
|||
switch sportID {
|
||||
case domain.FOOTBALL:
|
||||
result, err = s.parseFootball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse football", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
case domain.BASKETBALL:
|
||||
result, err = s.parseBasketball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse basketball", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
case domain.ICE_HOCKEY:
|
||||
result, err = s.parseIceHockey(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse ice hockey", "event id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
case domain.AMERICAN_FOOTBALL:
|
||||
result, err = s.parseNFL(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.RUGBY_UNION:
|
||||
result, err = s.parseRugbyUnion(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.RUGBY_LEAGUE:
|
||||
result, err = s.parseRugbyLeague(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.BASEBALL:
|
||||
result, err = s.parseBaseball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
default:
|
||||
s.logger.Error("Unsupported sport", "sport", sportID)
|
||||
return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) parseTimeStatus(timeStatusStr string) (bool, error) {
|
||||
timeStatusParsed, err := strconv.ParseInt(strings.TrimSpace(timeStatusStr), 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse time status", "time_status", timeStatusStr, "error", err)
|
||||
return false, fmt.Errorf("failed to parse time status: %w", err)
|
||||
}
|
||||
timeStatus := domain.TimeStatus(timeStatusParsed)
|
||||
|
||||
switch timeStatus {
|
||||
case domain.TIME_STATUS_NOT_STARTED, domain.TIME_STATUS_IN_PLAY, domain.TIME_STATUS_TO_BE_FIXED, domain.TIME_STATUS_ENDED:
|
||||
return true, nil
|
||||
case domain.TIME_STATUS_POSTPONED,
|
||||
domain.TIME_STATUS_CANCELLED,
|
||||
domain.TIME_STATUS_WALKOVER,
|
||||
domain.TIME_STATUS_INTERRUPTED,
|
||||
domain.TIME_STATUS_ABANDONED,
|
||||
domain.TIME_STATUS_RETIRED,
|
||||
domain.TIME_STATUS_SUSPENDED,
|
||||
domain.TIME_STATUS_DECIDED_BY_FA,
|
||||
domain.TIME_STATUS_REMOVED:
|
||||
return false, nil
|
||||
default:
|
||||
s.logger.Error("Invalid time status", "time_status", timeStatus)
|
||||
return false, fmt.Errorf("invalid time status: %d", timeStatus)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -235,18 +316,26 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
|
|||
return domain.CreateResult{}, err
|
||||
}
|
||||
result := fbResp
|
||||
if result.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
|
||||
isEventActive, err := s.parseTimeStatus(result.TimeStatus)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if !isEventActive {
|
||||
s.logger.Warn("Event is not active", "event_id", eventID)
|
||||
return domain.CreateResult{}, ErrEventIsNotActive
|
||||
}
|
||||
|
||||
finalScore := parseSS(result.SS)
|
||||
firstHalfScore := parseSS(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
||||
firstHalfScore := parseScore(result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away)
|
||||
secondHalfScore := parseScore(result.Scores.SecondHalf.Home, result.Scores.SecondHalf.Away)
|
||||
|
||||
corners := parseStats(result.Stats.Corners)
|
||||
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
||||
halfTimeCorners := parseStats(result.Stats.HalfTimeCorners)
|
||||
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, result.Events)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
s.logger.Error("Failed to evaluate football outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
|
|
@ -264,12 +353,17 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
|
|||
func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var basketBallRes domain.BasketballResultResponse
|
||||
if err := json.Unmarshal(response, &basketBallRes); err != nil {
|
||||
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||
s.logger.Error("Failed to unmarshal basketball result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if basketBallRes.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
isEventActive, err := s.parseTimeStatus(basketBallRes.TimeStatus)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if !isEventActive {
|
||||
s.logger.Warn("Event is not active", "event_id", eventID)
|
||||
return domain.CreateResult{}, ErrEventIsNotActive
|
||||
}
|
||||
|
||||
status, err := s.evaluateBasketballOutcome(outcome, basketBallRes)
|
||||
|
|
@ -292,12 +386,17 @@ func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, mark
|
|||
func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var iceHockeyRes domain.IceHockeyResultResponse
|
||||
if err := json.Unmarshal(response, &iceHockeyRes); err != nil {
|
||||
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||
s.logger.Error("Failed to unmarshal ice hockey result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if iceHockeyRes.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
isEventActive, err := s.parseTimeStatus(iceHockeyRes.TimeStatus)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if !isEventActive {
|
||||
s.logger.Warn("Event is not active", "event_id", eventID)
|
||||
return domain.CreateResult{}, ErrEventIsNotActive
|
||||
}
|
||||
|
||||
status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes)
|
||||
|
|
@ -317,6 +416,124 @@ func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marke
|
|||
|
||||
}
|
||||
|
||||
func (s *Service) parseNFL(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var nflResp domain.NFLResultResponse
|
||||
if err := json.Unmarshal(resultRes, &nflResp); err != nil {
|
||||
s.logger.Error("Failed to unmarshal NFL result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
if nflResp.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
|
||||
finalScore := parseSS(nflResp.SS)
|
||||
|
||||
var status domain.OutcomeStatus
|
||||
var err error
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
status, err = evaluateNFLMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
status, err = evaluateNFLSpread(outcome, finalScore)
|
||||
case "Total Points":
|
||||
status, err = evaluateNFLTotalPoints(outcome, finalScore)
|
||||
default:
|
||||
return domain.CreateResult{}, fmt.Errorf("unsupported market: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
return domain.CreateResult{
|
||||
BetOutcomeID: outcome.ID,
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
Status: status,
|
||||
Score: nflResp.SS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) parseRugbyUnion(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var rugbyResp domain.RugbyResultResponse
|
||||
if err := json.Unmarshal(resultRes, &rugbyResp); err != nil {
|
||||
s.logger.Error("Failed to unmarshal Rugby Union result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if rugbyResp.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
status, err := evaluateRugbyOutcome(outcome, &rugbyResp)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
return domain.CreateResult{
|
||||
BetOutcomeID: outcome.ID,
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
Status: status,
|
||||
Score: rugbyResp.SS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) parseRugbyLeague(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var rugbyResp domain.RugbyResultResponse
|
||||
if err := json.Unmarshal(resultRes, &rugbyResp); err != nil {
|
||||
s.logger.Error("Failed to unmarshal Rugby League result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if rugbyResp.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
status, err := evaluateRugbyOutcome(outcome, &rugbyResp)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
return domain.CreateResult{
|
||||
BetOutcomeID: outcome.ID,
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
Status: status,
|
||||
Score: rugbyResp.SS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) parseBaseball(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
var baseballResp domain.BaseballResultResponse
|
||||
if err := json.Unmarshal(resultRes, &baseballResp); err != nil {
|
||||
s.logger.Error("Failed to unmarshal Baseball result", "event_id", eventID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
if baseballResp.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
status, err := evaluateBaseballOutcome(outcome, &baseballResp)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
return domain.CreateResult{
|
||||
BetOutcomeID: outcome.ID,
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
Status: status,
|
||||
Score: baseballResp.SS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseScore(home string, away string) struct{ Home, Away int } {
|
||||
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
|
||||
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
|
||||
|
|
@ -343,7 +560,10 @@ func parseStats(stats []string) struct{ Home, Away int } {
|
|||
}
|
||||
|
||||
// evaluateOutcome determines the outcome status based on market type and odd
|
||||
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore struct{ Home, Away int }, corners struct{ Home, Away int }, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
|
||||
firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int },
|
||||
corners struct{ Home, Away int }, halfTimeCorners struct{ Home, Away int },
|
||||
events []map[string]string) (domain.OutcomeStatus, error) {
|
||||
|
||||
if !domain.SupportedMarkets[outcome.MarketID] {
|
||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||
|
|
@ -375,6 +595,21 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
|
|||
return evaluateDoubleChance(outcome, finalScore)
|
||||
case int64(domain.FOOTBALL_DRAW_NO_BET):
|
||||
return evaluateDrawNoBet(outcome, finalScore)
|
||||
case int64(domain.FOOTBALL_CORNERS):
|
||||
return evaluateCorners(outcome, corners)
|
||||
case int64(domain.FOOTBALL_CORNERS_TWO_WAY):
|
||||
return evaluateCorners(outcome, corners)
|
||||
case int64(domain.FOOTBALL_FIRST_HALF_CORNERS):
|
||||
return evaluateCorners(outcome, halfTimeCorners)
|
||||
case int64(domain.FOOTBALL_ASIAN_TOTAL_CORNERS):
|
||||
return evaluateCorners(outcome, corners)
|
||||
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_CORNERS):
|
||||
return evaluateCorners(outcome, halfTimeCorners)
|
||||
case int64(domain.FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN):
|
||||
return evaluateGoalsOddEven(outcome, firstHalfScore)
|
||||
case int64(domain.FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN):
|
||||
return evaluateGoalsOddEven(outcome, secondHalfScore)
|
||||
|
||||
default:
|
||||
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
||||
|
|
@ -411,7 +646,9 @@ func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domai
|
|||
case int64(domain.BASKETBALL_GAME_TOTAL_ODD_EVEN):
|
||||
return evaluateGoalsOddEven(outcome, finalScore)
|
||||
case int64(domain.BASKETBALL_TEAM_TOTALS):
|
||||
return evaluateGoalsOddEven(outcome, finalScore)
|
||||
return evaluateTeamTotal(outcome, finalScore)
|
||||
case int64(domain.BASKETBALL_TEAM_TOTAL_ODD_EVEN):
|
||||
return evaluateTeamOddEven(outcome, finalScore)
|
||||
|
||||
case int64(domain.BASKETBALL_FIRST_HALF):
|
||||
return evaluateGameLines(outcome, firstHalfScore)
|
||||
|
|
@ -442,6 +679,11 @@ func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domai
|
|||
return evaluateDoubleChance(outcome, firstQuarter)
|
||||
case int64(domain.BASKETBALL_HIGHEST_SCORING_QUARTER):
|
||||
return evaluateHighestScoringQuarter(outcome, firstQuarter, secondQuarter, thirdQuarter, fourthQuarter)
|
||||
case int64(domain.BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL):
|
||||
return evaluateResultAndTotal(outcome, firstQuarter)
|
||||
|
||||
case int64(domain.BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER):
|
||||
return evaluateTeamWithHighestScoringQuarter(outcome, firstQuarter, secondQuarter, thirdQuarter, fourthQuarter)
|
||||
|
||||
default:
|
||||
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
||||
|
|
@ -487,3 +729,22 @@ func (s *Service) evaluateIceHockeyOutcome(outcome domain.BetOutcome, res domain
|
|||
|
||||
return domain.OUTCOME_STATUS_PENDING, nil
|
||||
}
|
||||
|
||||
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, finalScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
if !domain.SupportedMarkets[outcome.MarketID] {
|
||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
switch outcome.MarketID {
|
||||
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||
return evaluateNFLMoneyLine(outcome, finalScore)
|
||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||
return evaluateNFLSpread(outcome, finalScore)
|
||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||
return evaluateNFLTotalPoints(outcome, finalScore)
|
||||
default:
|
||||
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package result
|
||||
280
internal/services/result/sports_eval.go
Normal file
280
internal/services/result/sports_eval.go
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
// NFL evaluations
|
||||
func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalPoints > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalPoints < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateRugbyMoneyLine evaluates Rugby money line bets
|
||||
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateRugbySpread evaluates Rugby spread bets
|
||||
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateRugbyTotalPoints evaluates Rugby total points bets
|
||||
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalPoints > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalPoints < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballMoneyLine evaluates Baseball money line bets
|
||||
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballSpread evaluates Baseball spread bets
|
||||
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateBaseballTotalRuns evaluates Baseball total runs bets
|
||||
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalRuns := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalRuns > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalRuns == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalRuns < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalRuns == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballFirstInning evaluates Baseball first inning bets
|
||||
func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "X":
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballFirst5Innings evaluates Baseball first 5 innings bets
|
||||
func evaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "X":
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
303
internal/services/result/sports_eval_test.go
Normal file
303
internal/services/result/sports_eval_test.go
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNFLMarkets covers all American Football (NFL) market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Total Points), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestNFLMarkets(t *testing.T) {
|
||||
t.Log("Testing NFL (American Football) Markets")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.AMERICAN_FOOTBALL_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.AMERICAN_FOOTBALL_SPREAD), "SPREAD"},
|
||||
{int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS), "TOTAL_POINTS"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given NFL market.
|
||||
switch m.marketID {
|
||||
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 14, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 17, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-3.5"}, struct{ Home, Away int }{Home: 24, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+3.5"}, struct{ Home, Away int }{Home: 20, Away: 24})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "44.5"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "44.5"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "37"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRugbyMarkets covers all Rugby (Union & League) market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Handicap, Total Points), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestRugbyMarkets(t *testing.T) {
|
||||
t.Log("Testing Rugby Markets (Union & League)")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.RUGBY_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.RUGBY_SPREAD), "SPREAD"},
|
||||
{int64(domain.RUGBY_TOTAL_POINTS), "TOTAL_POINTS"},
|
||||
{int64(domain.RUGBY_HANDICAP), "HANDICAP"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given Rugby market.
|
||||
switch m.marketID {
|
||||
case int64(domain.RUGBY_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 20, Away: 30})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 25, Away: 25})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_SPREAD), int64(domain.RUGBY_HANDICAP):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-6.5"}, struct{ Home, Away int }{Home: 28, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+6.5"}, struct{ Home, Away int }{Home: 20, Away: 28})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_TOTAL_POINTS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "40.5"}, struct{ Home, Away int }{Home: 25, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "40.5"}, struct{ Home, Away int }{Home: 15, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "35"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseballMarkets covers all Baseball market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Total Runs), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestBaseballMarkets(t *testing.T) {
|
||||
t.Log("Testing Baseball Markets")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.BASEBALL_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.BASEBALL_SPREAD), "SPREAD"},
|
||||
{int64(domain.BASEBALL_TOTAL_RUNS), "TOTAL_RUNS"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given Baseball market.
|
||||
switch m.marketID {
|
||||
case int64(domain.BASEBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 6, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 2, Away: 5})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_SPREAD):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-1.5"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+1.5"}, struct{ Home, Away int }{Home: 3, Away: 5})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_TOTAL_RUNS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7.5"}, struct{ Home, Away int }{Home: 5, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "7.5"}, struct{ Home, Away int }{Home: 2, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
189
internal/services/result_checker.go
Normal file
189
internal/services/result_checker.go
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
// ResultCheckerService handles the checking of game results
|
||||
type ResultCheckerService struct {
|
||||
// Add any dependencies here (e.g., repositories, external APIs)
|
||||
}
|
||||
|
||||
// NewResultCheckerService creates a new instance of ResultCheckerService
|
||||
func NewResultCheckerService() *ResultCheckerService {
|
||||
return &ResultCheckerService{}
|
||||
}
|
||||
|
||||
// CheckNFLResult checks the result of an NFL game
|
||||
func (s *ResultCheckerService) CheckNFLResult(data json.RawMessage) (*domain.Result, error) {
|
||||
nflResult, err := domain.ParseNFLResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse NFL result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetNFLWinner(nflResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine NFL winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatNFLScore(nflResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, nflResult.Home.Name, nflResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: nflResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": nflResult.Scores.FirstQuarter,
|
||||
"2": nflResult.Scores.SecondQuarter,
|
||||
"3": nflResult.Scores.ThirdQuarter,
|
||||
"4": nflResult.Scores.FourthQuarter,
|
||||
"5": nflResult.Scores.Overtime,
|
||||
"7": nflResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// determineOutcomeStatus determines the outcome status based on the winner and teams
|
||||
func determineOutcomeStatus(winner, homeTeam, awayTeam string) domain.OutcomeStatus {
|
||||
if winner == "Draw" {
|
||||
return domain.OUTCOME_STATUS_VOID
|
||||
}
|
||||
if winner == homeTeam {
|
||||
return domain.OUTCOME_STATUS_WIN
|
||||
}
|
||||
if winner == awayTeam {
|
||||
return domain.OUTCOME_STATUS_LOSS
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING
|
||||
}
|
||||
|
||||
// CheckRugbyResult checks the result of a Rugby game
|
||||
func (s *ResultCheckerService) CheckRugbyResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckBaseballResult checks the result of a Baseball game
|
||||
func (s *ResultCheckerService) CheckBaseballResult(data json.RawMessage) (*domain.Result, error) {
|
||||
baseballResult, err := domain.ParseBaseballResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Baseball result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetBaseballWinner(baseballResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Baseball winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatBaseballScore(baseballResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, baseballResult.Home.Name, baseballResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: baseballResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": baseballResult.Scores.FirstInning,
|
||||
"2": baseballResult.Scores.SecondInning,
|
||||
"3": baseballResult.Scores.ThirdInning,
|
||||
"4": baseballResult.Scores.FourthInning,
|
||||
"5": baseballResult.Scores.FifthInning,
|
||||
"6": baseballResult.Scores.SixthInning,
|
||||
"7": baseballResult.Scores.SeventhInning,
|
||||
"8": baseballResult.Scores.EighthInning,
|
||||
"9": baseballResult.Scores.NinthInning,
|
||||
"10": baseballResult.Scores.ExtraInnings,
|
||||
"T": baseballResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckRugbyUnionResult checks the result of a Rugby Union game
|
||||
func (s *ResultCheckerService) CheckRugbyUnionResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyUnionResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby Union result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby Union winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckRugbyLeagueResult checks the result of a Rugby League game
|
||||
func (s *ResultCheckerService) CheckRugbyLeagueResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyLeagueResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby League result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby League winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -37,3 +37,7 @@ func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, statu
|
|||
func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
|
||||
return s.ticketStore.DeleteTicket(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteOldTickets(ctx context.Context) error {
|
||||
return s.ticketStore.DeleteOldTickets(ctx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
|
|||
type Filter struct {
|
||||
Role string
|
||||
CompanyID domain.ValidInt64
|
||||
Page int
|
||||
PageSize int
|
||||
Page domain.ValidInt
|
||||
PageSize domain.ValidInt
|
||||
}
|
||||
type ValidRole struct {
|
||||
Value domain.Role
|
||||
|
|
@ -71,6 +71,10 @@ func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]do
|
|||
return s.userStore.GetCashiersByBranch(ctx, branchID)
|
||||
}
|
||||
|
||||
func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
|
||||
func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) {
|
||||
return s.userStore.GetAllCashiers(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) {
|
||||
return s.userStore.GetCashierByID(ctx, cashierID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@ type UserStore interface {
|
|||
CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
|
||||
GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||
GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, int64, error)
|
||||
GetAllCashiers(ctx context.Context) ([]domain.User, error)
|
||||
GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error)
|
||||
GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error)
|
||||
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
|
||||
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||
UpdateUserCompany(ctx context.Context, id int64, companyID int64) error
|
||||
UpdateUserSuspend(ctx context.Context, id int64, status bool) 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)
|
||||
SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
|
||||
UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error // identifier verified email or phone
|
||||
}
|
||||
type SmsGateway interface {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ type Service struct {
|
|||
otpStore OtpStore
|
||||
smsGateway SmsGateway
|
||||
emailGateway EmailGateway
|
||||
|
||||
}
|
||||
|
||||
func NewService(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string) ([]domain.User, error) {
|
||||
func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error) {
|
||||
// Search user
|
||||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString)
|
||||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, role, companyID)
|
||||
|
||||
}
|
||||
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||
|
|
@ -20,7 +20,11 @@ func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) err
|
|||
func (s *Service) UpdateUserCompany(ctx context.Context, id int64, companyID int64) error {
|
||||
// update user
|
||||
return s.userStore.UpdateUserCompany(ctx, id, companyID)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
// update user
|
||||
return s.userStore.UpdateUserSuspend(ctx, id, status)
|
||||
}
|
||||
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||
return s.userStore.GetUserByID(ctx, id)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ type WalletStore interface {
|
|||
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
||||
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)
|
||||
GetCustomerWallet(ctx context.Context, customerID 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
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet)
|
|||
return s.walletStore.CreateWallet(ctx, wallet)
|
||||
}
|
||||
|
||||
func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) {
|
||||
func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64) (domain.CustomerWallet, error) {
|
||||
|
||||
regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{
|
||||
IsWithdraw: true,
|
||||
|
|
@ -39,7 +39,6 @@ func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, co
|
|||
|
||||
return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{
|
||||
CustomerID: customerID,
|
||||
CompanyID: companyID,
|
||||
RegularWalletID: regularWallet.ID,
|
||||
StaticWalletID: staticWallet.ID,
|
||||
})
|
||||
|
|
@ -57,8 +56,8 @@ func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wall
|
|||
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) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
|
||||
return s.walletStore.GetCustomerWallet(ctx, customerID)
|
||||
}
|
||||
|
||||
func (s *Service) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) {
|
||||
|
|
@ -91,8 +90,6 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,16 +31,17 @@ import (
|
|||
|
||||
type App struct {
|
||||
fiber *fiber.App
|
||||
aleaVirtualGameService alea.AleaVirtualGameService
|
||||
veliVirtualGameService veli.VeliVirtualGameService
|
||||
cfg *config.Config
|
||||
logger *slog.Logger
|
||||
NotidicationStore notificationservice.NotificationStore
|
||||
NotidicationStore *notificationservice.Service
|
||||
referralSvc referralservice.ReferralStore
|
||||
port int
|
||||
authSvc *authentication.Service
|
||||
userSvc *user.Service
|
||||
betSvc *bet.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
aleaVirtualGameService alea.AleaVirtualGameService
|
||||
veliVirtualGameService veli.VeliVirtualGameService
|
||||
walletSvc *wallet.Service
|
||||
transactionSvc *transaction.Service
|
||||
ticketSvc *ticket.Service
|
||||
|
|
@ -52,7 +53,6 @@ type App struct {
|
|||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
resultSvc *result.Service
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewApp(
|
||||
|
|
@ -67,7 +67,7 @@ func NewApp(
|
|||
transactionSvc *transaction.Service,
|
||||
branchSvc *branch.Service,
|
||||
companySvc *company.Service,
|
||||
notidicationStore notificationservice.NotificationStore,
|
||||
notidicationStore *notificationservice.Service,
|
||||
prematchSvc *odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
|
|
@ -85,9 +85,9 @@ func NewApp(
|
|||
})
|
||||
|
||||
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
|
||||
AllowOrigins: "*",
|
||||
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
|
||||
AllowHeaders: "Content-Type,Authorization,platform",
|
||||
// AllowCredentials: true,
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
// "context"
|
||||
"context"
|
||||
|
||||
"log"
|
||||
|
||||
// "time"
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
|
|
@ -20,53 +21,24 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "0 0 * * * *", // Every 1 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)
|
||||
}
|
||||
},
|
||||
},
|
||||
// {
|
||||
// spec: "0 */15 * * * *",
|
||||
// spec: "0 0 * * * *", // Every 1 hour
|
||||
// task: func() {
|
||||
// log.Println("Fetching results for upcoming events...")
|
||||
|
||||
// upcomingEvents, err := eventService.GetAllUpcomingEvents(context.Background())
|
||||
// if err != nil {
|
||||
// log.Printf("Failed to fetch upcoming events: %v", err)
|
||||
// return
|
||||
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
// log.Printf("FetchUpcomingEvents error: %v", err)
|
||||
// }
|
||||
|
||||
// for _, event := range upcomingEvents {
|
||||
// if err := resultService.FetchAndStoreResult(context.Background(), event.ID); err != nil {
|
||||
// log.Printf(" Failed to fetch/store result for event %s: %v", event.ID, err)
|
||||
// } else {
|
||||
// log.Printf(" Successfully stored result for event %s", event.ID)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// spec: "0 */15 * * * *", // Every 15 minutes
|
||||
// task: func() {
|
||||
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||
// log.Printf("FetchNonLiveOdds error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
spec: "0 */15 * * * *",
|
||||
spec: "0 */15 * * * *", // Every 15 Minutes
|
||||
task: func() {
|
||||
log.Println("Fetching results for upcoming events...")
|
||||
|
||||
|
|
@ -80,6 +52,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
job.task()
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
log.Fatalf("Failed to schedule cron job: %v", err)
|
||||
}
|
||||
|
|
@ -88,3 +61,33 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
c.Start()
|
||||
log.Println("Cron jobs started for event and odds services")
|
||||
}
|
||||
|
||||
func StartTicketCrons(ticketService ticket.Service) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "0 0 * * * *", // Every hour
|
||||
task: func() {
|
||||
log.Println("Deleting old tickets...")
|
||||
if err := ticketService.DeleteOldTickets(context.Background()); err != nil {
|
||||
log.Printf("Failed to remove old ticket: %v", err)
|
||||
} else {
|
||||
log.Printf("Successfully deleted old tickets")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
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 ticket service")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -124,14 +125,21 @@ type AdminRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /admin [get]
|
||||
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||
|
||||
filter := user.Filter{
|
||||
Role: string(domain.RoleAdmin),
|
||||
CompanyID: domain.ValidInt64{
|
||||
Value: int64(c.QueryInt("company_id")),
|
||||
Valid: false,
|
||||
},
|
||||
Page: domain.ValidInt{
|
||||
Value: c.QueryInt("page", 1) - 1,
|
||||
Valid: true,
|
||||
},
|
||||
PageSize: domain.ValidInt{
|
||||
Value: c.QueryInt("page_size", 10),
|
||||
Valid: true,
|
||||
},
|
||||
Page: c.QueryInt("page", 1) - 1,
|
||||
PageSize: c.QueryInt("page_size", 10),
|
||||
}
|
||||
valErrs, ok := h.validator.Validate(c, filter)
|
||||
if !ok {
|
||||
|
|
@ -171,6 +179,155 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page, int(total))
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total))
|
||||
}
|
||||
|
||||
// GetAdminByID godoc
|
||||
// @Summary Get admin by id
|
||||
// @Description Get a single admin by id
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} AdminRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /admin/{id} [get]
|
||||
func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
||||
// branchId := int64(12) //c.Locals("branch_id").(int64)
|
||||
// filter := user.Filter{
|
||||
// Role: string(domain.RoleUser),
|
||||
// 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)
|
||||
// }
|
||||
|
||||
userIDstr := c.Params("id")
|
||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to fetch user using UserID", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid admin ID", nil, nil)
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("Get User By ID failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get admin", nil, nil)
|
||||
}
|
||||
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
if err != nil {
|
||||
if err != authentication.ErrRefreshTokenNotFound {
|
||||
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
|
||||
}
|
||||
|
||||
lastLogin = &user.CreatedAt
|
||||
}
|
||||
|
||||
res := AdminRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
type updateAdminReq struct {
|
||||
FirstName string `json:"first_name" example:"John"`
|
||||
LastName string `json:"last_name" example:"Doe"`
|
||||
Suspended bool `json:"suspended" example:"false"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
// UpdateAdmin godoc
|
||||
// @Summary Update Admin
|
||||
// @Description Update Admin
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param admin body updateAdminReq true "Update Admin"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /admin/{id} [put]
|
||||
func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
||||
var req updateAdminReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("UpdateAdmin failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
|
||||
if !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
AdminIDStr := c.Params("id")
|
||||
AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("UpdateAdmin failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Admin ID", nil, nil)
|
||||
}
|
||||
var companyID domain.ValidInt64
|
||||
if req.CompanyID != nil {
|
||||
companyID = domain.ValidInt64{
|
||||
Value: *req.CompanyID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||
UserId: AdminID,
|
||||
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,
|
||||
},
|
||||
CompanyID: companyID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
h.logger.Error("UpdateAdmin failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update admin", nil, nil)
|
||||
}
|
||||
if req.CompanyID != nil {
|
||||
_, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
|
||||
ID: *req.CompanyID,
|
||||
AdminID: &AdminID,
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("CreateAdmin failed to update company", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update company", nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,98 +1,24 @@
|
|||
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/services/wallet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
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"`
|
||||
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,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
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.Float32(),
|
||||
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.Float32(),
|
||||
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
|
||||
// @Summary Create a bet
|
||||
// @Description Creates a bet
|
||||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param createBet body CreateBetReq true "Creates bet"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Param createBet body domain.CreateBetReq true "Creates bet"
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet [post]
|
||||
|
|
@ -102,7 +28,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
|||
userID := c.Locals("user_id").(int64)
|
||||
role := c.Locals("role").(domain.Role)
|
||||
|
||||
var req CreateBetReq
|
||||
var req domain.CreateBetReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse CreateBet request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
|
|
@ -113,199 +39,102 @@ func (h *Handler) CreateBet(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
|
||||
// Validation for creating tickets
|
||||
if len(req.Outcomes) > 30 {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
||||
res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role)
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("PlaceBet failed", "error", err)
|
||||
switch err {
|
||||
case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient:
|
||||
return fiber.NewError(fiber.StatusBadGateway, err.Error())
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet")
|
||||
}
|
||||
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
|
||||
var totalOdds float32 = 1
|
||||
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)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
|
||||
|
||||
}
|
||||
|
||||
// RandomBet godoc
|
||||
// @Summary Generate a random bet
|
||||
// @Description Generate a random bet
|
||||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param createBet body domain.RandomBetReq true "Create Random bet"
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /random/bet [post]
|
||||
func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||
|
||||
// Get user_id from middleware
|
||||
userID := c.Locals("user_id").(int64)
|
||||
// role := c.Locals("role").(domain.Role)
|
||||
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
sportIDQuery := c.Query("sport_id")
|
||||
firstStartTimeQuery := c.Query("first_start_time")
|
||||
lastStartTimeQuery := c.Query("last_start_time")
|
||||
|
||||
leagueID := domain.ValidString{
|
||||
Value: leagueIDQuery,
|
||||
Valid: leagueIDQuery != "",
|
||||
}
|
||||
sportID := domain.ValidString{
|
||||
Value: sportIDQuery,
|
||||
Valid: sportIDQuery != "",
|
||||
}
|
||||
|
||||
var firstStartTime domain.ValidTime
|
||||
if firstStartTimeQuery != "" {
|
||||
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
|
||||
h.logger.Error("invalid start_time format", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||
}
|
||||
firstStartTime = domain.ValidTime{
|
||||
Value: firstStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
var lastStartTime domain.ValidTime
|
||||
if lastStartTimeQuery != "" {
|
||||
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid start_time format", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||
}
|
||||
lastStartTime = domain.ValidTime{
|
||||
Value: lastStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// }
|
||||
var req domain.RandomBetReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse RandomBet request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
if !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
var res domain.CreateBetRes
|
||||
var err error
|
||||
for i := 0; i < int(req.NumberOfBets); i++ {
|
||||
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
|
||||
|
||||
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", "error", err)
|
||||
continue
|
||||
}
|
||||
if rawOdd.ID == oddIDStr {
|
||||
selectedOdd = rawOdd
|
||||
isOddFound = true
|
||||
h.logger.Error("Random Bet failed", "error", err)
|
||||
switch err {
|
||||
case bet.ErrNoEventsAvailable:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "No events found")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
|
||||
}
|
||||
|
||||
if !isOddFound {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
||||
}
|
||||
|
||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||
totalOdds = totalOdds * float32(parsedOdd)
|
||||
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid sport id", nil, nil)
|
||||
}
|
||||
|
||||
h.logger.Info("Create Bet", slog.Int64("sportId", sportID))
|
||||
|
||||
outcomes = append(outcomes, domain.CreateBetOutcome{
|
||||
EventID: outcome.EventID,
|
||||
OddID: outcome.OddID,
|
||||
MarketID: outcome.MarketID,
|
||||
SportID: sportID,
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
Odd: float32(parsedOdd),
|
||||
OddName: selectedOdd.Name,
|
||||
OddHeader: selectedOdd.Header,
|
||||
OddHandicap: selectedOdd.Handicap,
|
||||
Expires: event.StartTime,
|
||||
})
|
||||
}
|
||||
|
||||
// Validating user by role
|
||||
// Differentiating between offline and online bets
|
||||
cashoutID, err := h.betSvc.GenerateCashoutID()
|
||||
if err != nil {
|
||||
h.logger.Error("CreateBetReq failed, unable to create cashout id")
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid request", err, nil)
|
||||
}
|
||||
var bet domain.Bet
|
||||
if role == domain.RoleCashier {
|
||||
|
||||
// Get the branch from the branch ID
|
||||
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("CreateBetReq failed, branch id invalid")
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
||||
}
|
||||
|
||||
// Deduct a percentage of the amount
|
||||
// TODO move to service layer. Make it fetch dynamically from company
|
||||
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: totalOdds,
|
||||
Status: req.Status,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
|
||||
BranchID: domain.ValidInt64{
|
||||
Value: branch.ID,
|
||||
Valid: true,
|
||||
},
|
||||
UserID: domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: false,
|
||||
},
|
||||
IsShopBet: true,
|
||||
CashoutID: cashoutID,
|
||||
})
|
||||
} else if role == domain.RoleSuperAdmin || role == domain.RoleAdmin || role == domain.RoleBranchManager {
|
||||
// If a non cashier wants to create a bet, they will need to provide the Branch ID
|
||||
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
|
||||
if req.BranchID == nil {
|
||||
h.logger.Error("CreateBetReq failed, Branch ID is required for this type of user")
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this type of user", nil, nil)
|
||||
}
|
||||
// h.logger.Info("Branch ID", slog.Int64("branch_id", *req.BranchID))
|
||||
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
Status: req.Status,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
BranchID: domain.ValidInt64{
|
||||
Value: *req.BranchID,
|
||||
Valid: true,
|
||||
},
|
||||
UserID: domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: true,
|
||||
},
|
||||
IsShopBet: true,
|
||||
CashoutID: cashoutID,
|
||||
})
|
||||
} 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: 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: false,
|
||||
CashoutID: cashoutID,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("CreateBetReq failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
|
||||
}
|
||||
|
||||
// Updating the bet id for outcomes
|
||||
for index := range outcomes {
|
||||
outcomes[index].BetID = bet.ID
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
|
|
@ -316,7 +145,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
|||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} BetRes
|
||||
// @Success 200 {array} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet [get]
|
||||
|
|
@ -327,9 +156,9 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
|
||||
}
|
||||
|
||||
res := make([]BetRes, len(bets))
|
||||
res := make([]domain.BetRes, len(bets))
|
||||
for i, bet := range bets {
|
||||
res[i] = convertBet(bet)
|
||||
res[i] = domain.ConvertBet(bet)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
|
||||
|
|
@ -342,7 +171,7 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Bet ID"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet/{id} [get]
|
||||
|
|
@ -356,11 +185,12 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
|||
|
||||
bet, err := h.betSvc.GetBetByID(c.Context(), id)
|
||||
if err != nil {
|
||||
// TODO: handle all the errors types
|
||||
h.logger.Error("Failed to get bet by ID", "betID", id, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bet")
|
||||
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet")
|
||||
}
|
||||
|
||||
res := convertBet(bet)
|
||||
res := domain.ConvertBet(bet)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||
|
||||
|
|
@ -373,7 +203,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "cashout ID"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet/cashout/{id} [get]
|
||||
|
|
@ -392,7 +222,7 @@ func (h *Handler) GetBetByCashoutID(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
|
||||
}
|
||||
|
||||
res := convertBet(bet)
|
||||
res := domain.ConvertBet(bet)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
|
@ -141,7 +142,8 @@ func (h *Handler) CreateBranch(c *fiber.Ctx) error {
|
|||
checkedCompanyID = *req.CompanyID
|
||||
} else {
|
||||
IsSelfOwned = false
|
||||
checkedCompanyID = companyID.Value //the company id is always valid when its not a super admin
|
||||
checkedCompanyID = companyID.Value
|
||||
//TODO:check that the company id is always valid when its not a super admin
|
||||
}
|
||||
|
||||
// Create Branch Wallet
|
||||
|
|
@ -492,13 +494,71 @@ func (h *Handler) GetBranchOperations(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Branch Operations retrieved successfully", result, nil)
|
||||
}
|
||||
|
||||
// GetBranchCashiers godoc
|
||||
// @Summary Gets branch cashiers
|
||||
// @Description Gets branch cashiers
|
||||
// @Tags branch
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Branch ID"
|
||||
// @Success 200 {array} GetCashierRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /branch/{id}/cashier [get]
|
||||
func (h *Handler) GetBranchCashiers(c *fiber.Ctx) error {
|
||||
branchID := c.Params("id")
|
||||
id, err := strconv.ParseInt(branchID, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("Invalid branch ID", "branchID", branchID, "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil)
|
||||
}
|
||||
|
||||
cashiers, err := h.userSvc.GetCashiersByBranch(c.Context(), id)
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get cashier by branch ID", "branchID", id, "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier", err, nil)
|
||||
}
|
||||
|
||||
var result []GetCashierRes = make([]GetCashierRes, 0, len(cashiers))
|
||||
|
||||
for _, cashier := range cashiers {
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), cashier.ID)
|
||||
if err != nil {
|
||||
if err == authentication.ErrRefreshTokenNotFound {
|
||||
lastLogin = &cashier.CreatedAt
|
||||
} else {
|
||||
h.logger.Error("Failed to get user last login", "userID", cashier.ID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
|
||||
}
|
||||
}
|
||||
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,
|
||||
LastLogin: *lastLogin,
|
||||
})
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Branch Cashiers 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
|
||||
// @Success 200 {array} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /branch/{id}/bets [get]
|
||||
|
|
@ -517,9 +577,9 @@ func (h *Handler) GetBetByBranchID(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil)
|
||||
}
|
||||
|
||||
var res []BetRes = make([]BetRes, 0, len(bets))
|
||||
var res []domain.BetRes = make([]domain.BetRes, 0, len(bets))
|
||||
for _, bet := range bets {
|
||||
res = append(res, convertBet(bet))
|
||||
res = append(res, domain.ConvertBet(bet))
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Branch Bets Retrieved", res, nil)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
|
@ -87,6 +88,7 @@ type GetCashierRes struct {
|
|||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
BranchID int64 `json:"branch_id"`
|
||||
}
|
||||
|
||||
// GetAllCashiers godoc
|
||||
|
|
@ -103,22 +105,31 @@ type GetCashierRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /cashiers [get]
|
||||
func (h *Handler) GetAllCashiers(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)
|
||||
// }
|
||||
role := c.Locals("role").(domain.Role)
|
||||
companyId := c.Locals("company_id").(domain.ValidInt64)
|
||||
|
||||
cashiers, err := h.userSvc.GetAllCashiers(c.Context())
|
||||
if role != domain.RoleSuperAdmin && !companyId.Valid {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID")
|
||||
}
|
||||
filter := user.Filter{
|
||||
Role: string(domain.RoleCashier),
|
||||
CompanyID: companyId,
|
||||
Page: domain.ValidInt{
|
||||
Value: c.QueryInt("page", 1) - 1,
|
||||
Valid: true,
|
||||
},
|
||||
PageSize: domain.ValidInt{
|
||||
Value: c.QueryInt("page_size", 10),
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, filter)
|
||||
if !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
cashiers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
|
||||
if err != nil {
|
||||
h.logger.Error("GetAllCashiers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
|
||||
|
|
@ -154,11 +165,80 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil)
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil, filter.Page.Value, int(total))
|
||||
|
||||
}
|
||||
|
||||
type updateUserReq struct {
|
||||
// GetCashierByID godoc
|
||||
// @Summary Get cashier by id
|
||||
// @Description Get a single cashier by id
|
||||
// @Tags cashier
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} UserProfileRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /cashier/{id} [get]
|
||||
func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
|
||||
// branchId := int64(12) //c.Locals("branch_id").(int64)
|
||||
// filter := user.Filter{
|
||||
// Role: string(domain.RoleUser),
|
||||
// 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)
|
||||
// }
|
||||
|
||||
stringID := c.Params("id")
|
||||
cashierID, err := strconv.ParseInt(stringID, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to fetch user using UserID", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetCashierByID(c.Context(), cashierID)
|
||||
if err != nil {
|
||||
h.logger.Error("Get User By ID failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
|
||||
}
|
||||
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
if err != nil {
|
||||
if err != authentication.ErrRefreshTokenNotFound {
|
||||
h.logger.Error("Failed to get user last login", "cashierID", user.ID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
|
||||
}
|
||||
lastLogin = &user.CreatedAt
|
||||
}
|
||||
|
||||
res := GetCashierRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
BranchID: user.BranchID,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
type updateCashierReq struct {
|
||||
FirstName string `json:"first_name" example:"John"`
|
||||
LastName string `json:"last_name" example:"Doe"`
|
||||
Suspended bool `json:"suspended" example:"false"`
|
||||
|
|
@ -171,7 +251,7 @@ type updateUserReq struct {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Cashier ID"
|
||||
// @Param cashier body updateUserReq true "Update cashier"
|
||||
// @Param cashier body updateCashierReq true "Update cashier"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
|
|
@ -184,7 +264,7 @@ func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
|
|||
h.logger.Error("UpdateCashier failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
|
||||
}
|
||||
var req updateUserReq
|
||||
var req updateCashierReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("UpdateCashier failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,15 @@ type CompanyRes struct {
|
|||
}
|
||||
|
||||
type GetCompanyRes 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"`
|
||||
WalletBalance float32 `json:"balance" example:"1"`
|
||||
IsActive bool `json:"is_active" example:"false"`
|
||||
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"`
|
||||
WalletBalance float32 `json:"balance" example:"1"`
|
||||
IsActive bool `json:"is_active" example:"false"`
|
||||
AdminFirstName string `json:"admin_first_name" example:"John"`
|
||||
AdminLastName string `json:"admin_last_name" example:"Doe"`
|
||||
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
|
||||
}
|
||||
|
||||
func convertCompany(company domain.Company) CompanyRes {
|
||||
|
|
@ -44,12 +47,15 @@ func convertCompany(company domain.Company) CompanyRes {
|
|||
|
||||
func convertGetCompany(company domain.GetCompany) GetCompanyRes {
|
||||
return GetCompanyRes{
|
||||
ID: company.ID,
|
||||
Name: company.Name,
|
||||
AdminID: company.AdminID,
|
||||
WalletID: company.WalletID,
|
||||
WalletBalance: company.WalletBalance.Float32(),
|
||||
IsActive: company.IsWalletActive,
|
||||
ID: company.ID,
|
||||
Name: company.Name,
|
||||
AdminID: company.AdminID,
|
||||
WalletID: company.WalletID,
|
||||
WalletBalance: company.WalletBalance.Float32(),
|
||||
IsActive: company.IsWalletActive,
|
||||
AdminFirstName: company.AdminFirstName,
|
||||
AdminLastName: company.AdminLastName,
|
||||
AdminPhoneNumber: company.AdminPhoneNumber,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +241,7 @@ func (h *Handler) UpdateCompany(c *fiber.Ctx) error {
|
|||
|
||||
var req UpdateCompanyReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("CreateCompanyReq failed", "error", err)
|
||||
h.logger.Error("UpdateCompanyReq failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
||||
}
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
|
||||
type Handler struct {
|
||||
logger *slog.Logger
|
||||
notificationSvc notificationservice.NotificationStore
|
||||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
referralSvc referralservice.ReferralStore
|
||||
walletSvc *wallet.Service
|
||||
|
|
@ -47,7 +47,7 @@ type Handler struct {
|
|||
|
||||
func New(
|
||||
logger *slog.Logger,
|
||||
notificationSvc notificationservice.NotificationStore,
|
||||
notificationSvc *notificationservice.Service,
|
||||
validator *customvalidator.CustomValidator,
|
||||
walletSvc *wallet.Service,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
|
|
|
|||
|
|
@ -109,14 +109,23 @@ type ManagersRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /managers [get]
|
||||
func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
|
||||
role := c.Locals("role").(domain.Role)
|
||||
companyId := c.Locals("company_id").(domain.ValidInt64)
|
||||
|
||||
if role != domain.RoleSuperAdmin && !companyId.Valid {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID")
|
||||
}
|
||||
filter := user.Filter{
|
||||
Role: string(domain.RoleBranchManager),
|
||||
CompanyID: domain.ValidInt64{
|
||||
Value: int64(c.QueryInt("company_id")),
|
||||
Role: string(domain.RoleBranchManager),
|
||||
CompanyID: companyId,
|
||||
Page: domain.ValidInt{
|
||||
Value: c.QueryInt("page", 1) - 1,
|
||||
Valid: true,
|
||||
},
|
||||
PageSize: domain.ValidInt{
|
||||
Value: c.QueryInt("page_size", 10),
|
||||
Valid: true,
|
||||
},
|
||||
Page: c.QueryInt("page", 1) - 1,
|
||||
PageSize: c.QueryInt("page_size", 10),
|
||||
}
|
||||
valErrs, ok := h.validator.Validate(c, filter)
|
||||
if !ok {
|
||||
|
|
@ -156,24 +165,101 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page, int(total))
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page.Value, int(total))
|
||||
|
||||
}
|
||||
|
||||
// GetManagerByID godoc
|
||||
// @Summary Get manager by id
|
||||
// @Description Get a single manager by id
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} ManagersRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /managers/{id} [get]
|
||||
func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
|
||||
// branchId := int64(12) //c.Locals("branch_id").(int64)
|
||||
// filter := user.Filter{
|
||||
// Role: string(domain.RoleUser),
|
||||
// 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)
|
||||
// }
|
||||
|
||||
userIDstr := c.Params("id")
|
||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to fetch user using UserID", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid managers ID", nil, nil)
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("Get User By ID failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get managers", nil, nil)
|
||||
}
|
||||
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
if err != nil {
|
||||
if err != authentication.ErrRefreshTokenNotFound {
|
||||
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
|
||||
}
|
||||
|
||||
lastLogin = &user.CreatedAt
|
||||
}
|
||||
|
||||
res := ManagersRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
type updateManagerReq struct {
|
||||
FirstName string `json:"first_name" example:"John"`
|
||||
LastName string `json:"last_name" example:"Doe"`
|
||||
Suspended bool `json:"suspended" example:"false"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
// UpdateManagers godoc
|
||||
// @Summary Update Managers
|
||||
// @Description Update Managers
|
||||
// @Tags Managers
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Managers body updateUserReq true "Update Managers"
|
||||
// @Param Managers body updateManagerReq 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 (h *Handler) UpdateManagers(c *fiber.Ctx) error {
|
||||
var req updateUserReq
|
||||
var req updateManagerReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
|
|
@ -190,6 +276,19 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
|
|||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil)
|
||||
}
|
||||
var companyID domain.ValidInt64
|
||||
role := c.Locals("role").(domain.Role)
|
||||
if req.CompanyID != nil {
|
||||
if role != domain.RoleSuperAdmin {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusUnauthorized, "This user role cannot modify company ID", nil, nil)
|
||||
}
|
||||
companyID = domain.ValidInt64{
|
||||
Value: *req.CompanyID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||
UserId: ManagersId,
|
||||
FirstName: domain.ValidString{
|
||||
|
|
@ -204,6 +303,7 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
|
|||
Value: req.Suspended,
|
||||
Valid: true,
|
||||
},
|
||||
CompanyID: companyID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -3,58 +3,107 @@ package handlers
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/adaptor"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||
)
|
||||
|
||||
func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
|
||||
if !websocket.IsWebSocketUpgrade(c) {
|
||||
h.logger.Warn("WebSocket upgrade required")
|
||||
return fiber.ErrUpgradeRequired
|
||||
}
|
||||
func hijackHTTP(c *fiber.Ctx) (net.Conn, http.ResponseWriter, error) {
|
||||
var rw http.ResponseWriter
|
||||
var conn net.Conn
|
||||
|
||||
// This is a trick: fasthttpadaptor gives us the HTTP interfaces
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
conn, _, err = hj.Hijack()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rw = w
|
||||
})
|
||||
|
||||
fasthttpadaptor.NewFastHTTPHandler(handler)(c.Context())
|
||||
|
||||
if conn == nil || rw == nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to hijack connection")
|
||||
}
|
||||
return conn, rw, nil
|
||||
}
|
||||
|
||||
func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
|
||||
userID, ok := c.Locals("userID").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
|
||||
c.Locals("allowed", true)
|
||||
// Convert *fiber.Ctx to *http.Request
|
||||
req, err := adaptor.ConvertRequest(c, false)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to convert request", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert request")
|
||||
}
|
||||
|
||||
return websocket.New(func(conn *websocket.Conn) {
|
||||
ctx := context.Background()
|
||||
logger := h.logger.With("userID", userID, "remoteAddr", conn.RemoteAddr())
|
||||
// Create a net.Conn hijacked from the fasthttp context
|
||||
netConn, rw, err := hijackHTTP(c)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to hijack connection", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to hijack connection")
|
||||
}
|
||||
|
||||
if err := h.notificationSvc.ConnectWebSocket(ctx, userID, conn); err != nil {
|
||||
logger.Error("Failed to connect WebSocket", "error", err)
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
// Upgrade the connection using Gorilla's Upgrader
|
||||
conn, err := ws.Upgrader.Upgrade(rw, req, nil)
|
||||
if err != nil {
|
||||
h.logger.Error("WebSocket upgrade failed", "error", err)
|
||||
netConn.Close()
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "WebSocket upgrade failed")
|
||||
}
|
||||
|
||||
logger.Info("WebSocket connection established")
|
||||
client := &ws.Client{
|
||||
Conn: conn,
|
||||
RecipientID: userID,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
h.notificationSvc.DisconnectWebSocket(userID)
|
||||
logger.Info("WebSocket connection closed")
|
||||
_ = conn.Close()
|
||||
}()
|
||||
h.notificationSvc.Hub.Register <- client
|
||||
h.logger.Info("WebSocket connection established", "userID", userID)
|
||||
|
||||
for {
|
||||
if _, _, err := conn.ReadMessage(); err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
logger.Warn("WebSocket unexpected close", "error", err)
|
||||
}
|
||||
break
|
||||
defer func() {
|
||||
h.notificationSvc.Hub.Unregister <- client
|
||||
h.logger.Info("WebSocket connection closed", "userID", userID)
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, _, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
|
||||
h.logger.Info("WebSocket closed normally", "userID", userID)
|
||||
} else {
|
||||
h.logger.Warn("Unexpected WebSocket closure", "userID", userID, "error", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
})(c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
|
||||
type Request struct {
|
||||
NotificationID string `json:"notification_id" validate:"required"`
|
||||
NotificationIDs []string `json:"notification_ids" validate:"required"`
|
||||
}
|
||||
|
||||
var req Request
|
||||
|
|
@ -63,14 +112,15 @@ func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
userID, ok := c.Locals("userID").(int64)
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification")
|
||||
}
|
||||
|
||||
if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationID, userID); err != nil {
|
||||
h.logger.Error("Failed to mark notification as read", "notificationID", req.NotificationID, "error", err)
|
||||
fmt.Printf("Notification IDs: %v \n", req.NotificationIDs)
|
||||
if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil {
|
||||
h.logger.Error("Failed to mark notifications as read", "notificationID", req.NotificationIDs, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status")
|
||||
}
|
||||
|
||||
|
|
@ -97,18 +147,18 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
userID, ok := c.Locals("userID").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
// userID, ok := c.Locals("userID").(int64)
|
||||
// if !ok || userID == 0 {
|
||||
// h.logger.Error("[NotificationSvc.CreateAndSendNotification] Invalid user ID in context")
|
||||
// return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
// }
|
||||
|
||||
switch req.DeliveryScheme {
|
||||
case domain.NotificationDeliverySchemeSingle:
|
||||
if req.Reciever == domain.NotificationRecieverSideCustomer && req.RecipientID != userID {
|
||||
h.logger.Warn("[NotificationSvc.CreateAndSendNotification] Unauthorized attempt to send notification", "userID", userID, "recipientID", req.RecipientID)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Unauthorized to send notification to this recipient")
|
||||
}
|
||||
// if req.Reciever == domain.NotificationRecieverSideCustomer {
|
||||
// h.logger.Warn("[NotificationSvc.CreateAndSendNotification] Unauthorized attempt to send notification", "recipientID", req.RecipientID)
|
||||
// return fiber.NewError(fiber.StatusForbidden, "Unauthorized to send notification to this recipient")
|
||||
// }
|
||||
|
||||
notification := &domain.Notification{
|
||||
ID: "",
|
||||
|
|
@ -134,17 +184,21 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID})
|
||||
|
||||
case domain.NotificationDeliverySchemeBulk:
|
||||
recipients, err := h.getAllRecipientIDs(context.Background(), req.Reciever)
|
||||
recipients, _, err := h.userSvc.GetAllUsers(context.Background(), user.Filter{
|
||||
Role: string(req.Reciever),
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients")
|
||||
}
|
||||
|
||||
fmt.Printf("Number of Recipients %d \n", len(recipients))
|
||||
|
||||
notificationIDs := make([]string, 0, len(recipients))
|
||||
for _, recipientID := range recipients {
|
||||
for _, user := range recipients {
|
||||
notification := &domain.Notification{
|
||||
ID: "",
|
||||
RecipientID: recipientID,
|
||||
RecipientID: user.ID,
|
||||
Type: req.Type,
|
||||
Level: req.Level,
|
||||
ErrorSeverity: req.ErrorSeverity,
|
||||
|
|
@ -158,7 +212,7 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
|
||||
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "recipientID", recipientID, "error", err)
|
||||
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "UserID", user.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
notificationIDs = append(notificationIDs, notification.ID)
|
||||
|
|
@ -177,6 +231,94 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) getAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
|
||||
func (h *Handler) GetNotifications(c *fiber.Ctx) error {
|
||||
limitStr := c.Query("limit", "10")
|
||||
offsetStr := c.Query("offset", "0")
|
||||
|
||||
// Convert limit and offset to integers
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit <= 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid limit value", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
|
||||
}
|
||||
offset, err := strconv.Atoi(offsetStr)
|
||||
if err != nil || offset < 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid offset value", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid offset value")
|
||||
}
|
||||
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
|
||||
notifications, err := h.notificationSvc.ListNotifications(context.Background(), userID, limit, offset)
|
||||
if err != nil {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"notifications": notifications,
|
||||
"total_count": len(notifications),
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (h *Handler) GetAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
|
||||
return h.notificationSvc.ListRecipientIDs(ctx, receiver)
|
||||
}
|
||||
|
||||
func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error {
|
||||
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
|
||||
total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID)
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"unread": total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetAllNotifications(c *fiber.Ctx) error {
|
||||
limitStr := c.Query("limit", "10")
|
||||
pageStr := c.Query("page", "1")
|
||||
|
||||
// Convert limit and offset to integers
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit <= 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid limit value", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
|
||||
}
|
||||
page, err := strconv.Atoi(pageStr)
|
||||
if err != nil || page <= 0 {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Invalid page value", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid page value")
|
||||
}
|
||||
|
||||
notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit))
|
||||
if err != nil {
|
||||
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"notifications": notifications,
|
||||
"total_count": len(notifications),
|
||||
"limit": limit,
|
||||
"page": page,
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
|
|
@ -98,6 +99,8 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
|
|||
// @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"
|
||||
// @Param first_start_time query string false "Start Time"
|
||||
// @Param last_start_time query string false "End Time"
|
||||
// @Success 200 {array} domain.UpcomingEvent
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /prematch/events [get]
|
||||
|
|
@ -106,6 +109,8 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
pageSize := c.QueryInt("page_size", 10)
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
sportIDQuery := c.Query("sport_id")
|
||||
firstStartTimeQuery := c.Query("first_start_time")
|
||||
lastStartTimeQuery := c.Query("last_start_time")
|
||||
|
||||
leagueID := domain.ValidString{
|
||||
Value: leagueIDQuery,
|
||||
|
|
@ -116,7 +121,42 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
Valid: sportIDQuery != "",
|
||||
}
|
||||
|
||||
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page)-1, leagueID, sportID)
|
||||
var firstStartTime domain.ValidTime
|
||||
if firstStartTimeQuery != "" {
|
||||
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid start_time format", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||
}
|
||||
firstStartTime = domain.ValidTime{
|
||||
Value: firstStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
var lastStartTime domain.ValidTime
|
||||
if lastStartTimeQuery != "" {
|
||||
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid start_time format", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||
}
|
||||
lastStartTime = domain.ValidTime{
|
||||
Value: lastStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
limit := domain.ValidInt64{
|
||||
Value: int64(pageSize),
|
||||
Valid: true,
|
||||
}
|
||||
offset := domain.ValidInt64{
|
||||
Value: int64(page - 1),
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(
|
||||
c.Context(), limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
|
||||
|
||||
// fmt.Printf("League ID: %v", leagueID)
|
||||
if err != nil {
|
||||
|
|
@ -183,7 +223,7 @@ func (h *Handler) GetPrematchOddsByUpcomingID(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil)
|
||||
}
|
||||
|
||||
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID, int32(limit), int32(offset))
|
||||
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package handlers
|
|||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
|
|
@ -76,10 +77,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
// 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)
|
||||
// }
|
||||
currentTime := time.Now()
|
||||
if event.StartTime.Before(currentTime) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
|
||||
}
|
||||
|
||||
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
|
||||
|
||||
|
|
@ -182,7 +183,7 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
|
|||
|
||||
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
|
||||
if err != nil {
|
||||
// h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
|
||||
h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
|
||||
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,9 +150,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|||
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||
if err != nil {
|
||||
h.logger.Error("RegisterUser failed", "error", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
user.OtpMedium = medium
|
||||
|
|
@ -160,24 +158,22 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|||
newUser, err := h.userSvc.RegisterUser(c.Context(), user)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrOtpAlreadyUsed) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Otp already used")
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpExpired) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp expired", nil, nil)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Otp expired")
|
||||
}
|
||||
if errors.Is(err, domain.ErrInvalidOtp) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid otp", nil, nil)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid otp")
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpNotFound) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "User already exist")
|
||||
}
|
||||
h.logger.Error("RegisterUser failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error")
|
||||
}
|
||||
|
||||
_, err = h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
|
||||
newWallet, err := h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
|
||||
UserID: newUser.ID,
|
||||
IsWithdraw: true,
|
||||
IsBettable: true,
|
||||
|
|
@ -194,6 +190,14 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Remove later
|
||||
err = h.walletSvc.AddToWallet(c.Context(), newWallet.ID, domain.ToCurrency(100.0))
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet")
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
|
||||
}
|
||||
|
||||
|
|
@ -377,7 +381,8 @@ func getMedium(email, phoneNumber string) (domain.OtpMedium, error) {
|
|||
}
|
||||
|
||||
type SearchUserByNameOrPhoneReq struct {
|
||||
SearchString string
|
||||
SearchString string `json:"query"`
|
||||
Role *domain.Role `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
// SearchUserByNameOrPhone godoc
|
||||
|
|
@ -405,7 +410,8 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
|
|||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString)
|
||||
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID)
|
||||
if err != nil {
|
||||
h.logger.Error("SearchUserByNameOrPhone failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
|
|
@ -450,7 +456,7 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Success 200 {object} UserProfileRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
|
|
@ -474,13 +480,13 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error {
|
|||
userIDstr := c.Params("id")
|
||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("UpdateCashier failed", "error", err)
|
||||
h.logger.Error("failed to fetch user using UserID", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("GetAllCashiers failed", "error", err)
|
||||
h.logger.Error("Get User By ID failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
|
||||
}
|
||||
|
||||
|
|
@ -510,6 +516,78 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error {
|
|||
LastLogin: *lastLogin,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", res, nil)
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
|
||||
|
||||
}
|
||||
|
||||
// DeleteUser godoc
|
||||
// @Summary Delete user by ID
|
||||
// @Description Delete a user by their ID
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /user/delete/{id} [delete]
|
||||
func (h *Handler) DeleteUser(c *fiber.Ctx) error {
|
||||
userIDstr := c.Params("id")
|
||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("DeleteUser failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid user ID", nil, nil)
|
||||
}
|
||||
|
||||
err = h.userSvc.DeleteUser(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to delete user", "userID", userID, "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to delete user", nil, nil)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User deleted successfully", nil, nil)
|
||||
}
|
||||
|
||||
type UpdateUserSuspendReq struct {
|
||||
UserID int64 `json:"user_id" validate:"required" example:"123"`
|
||||
Suspended bool `json:"suspended" validate:"required" example:"true"`
|
||||
}
|
||||
type UpdateUserSuspendRes struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Suspended bool `json:"suspended"`
|
||||
}
|
||||
|
||||
// UpdateUserSuspend godoc
|
||||
// @Summary Suspend or unsuspend a user
|
||||
// @Description Suspend or unsuspend a user
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param updateUserSuspend body UpdateUserSuspendReq true "Suspend or unsuspend a user"
|
||||
// @Success 200 {object} UpdateUserSuspendRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /user/suspend [post]
|
||||
func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
|
||||
var req UpdateUserSuspendReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse UpdateUserSuspend request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to update user suspend status", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user suspend status")
|
||||
}
|
||||
|
||||
res := UpdateUserSuspendRes{
|
||||
UserID: req.UserID,
|
||||
Suspended: req.Suspended,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,13 +45,12 @@ type CustomerWalletRes struct {
|
|||
StaticID int64 `json:"static_id" example:"1"`
|
||||
StaticBalance float32 `json:"static_balance" example:"100.0"`
|
||||
CustomerID int64 `json:"customer_id" example:"1"`
|
||||
CompanyID int64 `json:"company_id" example:"1"`
|
||||
RegularUpdatedAt time.Time `json:"regular_updated_at"`
|
||||
StaticUpdatedAt time.Time `json:"static_updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
|
||||
func ConvertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
|
||||
return CustomerWalletRes{
|
||||
ID: wallet.ID,
|
||||
RegularID: wallet.RegularID,
|
||||
|
|
@ -59,7 +58,6 @@ func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
|
|||
StaticID: wallet.StaticID,
|
||||
StaticBalance: wallet.StaticBalance.Float32(),
|
||||
CustomerID: wallet.CustomerID,
|
||||
CompanyID: wallet.CompanyID,
|
||||
RegularUpdatedAt: wallet.RegularUpdatedAt,
|
||||
StaticUpdatedAt: wallet.StaticUpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
|
|
@ -249,21 +247,21 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized access")
|
||||
}
|
||||
|
||||
companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("Invalid company_id header", "value", c.Get("company_id"), "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
|
||||
}
|
||||
// companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
|
||||
// if err != nil {
|
||||
// h.logger.Error("Invalid company_id header", "value", c.Get("company_id"), "error", err)
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
|
||||
// }
|
||||
|
||||
h.logger.Info("Fetching customer wallet", "userID", userID, "companyID", companyID)
|
||||
h.logger.Info("Fetching customer wallet", "userID", userID)
|
||||
|
||||
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID, companyID)
|
||||
wallet, err := h.walletSvc.GetWalletsByUser(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get customer wallet", "userID", userID, "companyID", companyID, "error", err)
|
||||
h.logger.Error("Failed to get customer wallet", "userID", userID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
|
||||
}
|
||||
|
||||
res := convertCustomerWallet(wallet)
|
||||
res := convertWallet(wallet[0])
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
|
|||
|
||||
}
|
||||
// Asserting to make sure that there is no company role without a valid company id
|
||||
if claim.Role != domain.RoleSuperAdmin && !claim.CompanyID.Valid {
|
||||
if claim.Role != domain.RoleSuperAdmin && claim.Role != domain.RoleCustomer && !claim.CompanyID.Valid {
|
||||
fmt.Println("Company Role without Company ID")
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Company Role without Company ID")
|
||||
}
|
||||
|
|
@ -71,3 +71,31 @@ func (a *App) CompanyOnly(c *fiber.Ctx) error {
|
|||
}
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func (a *App) WebsocketAuthMiddleware(c *fiber.Ctx) error {
|
||||
tokenStr := c.Query("token")
|
||||
if tokenStr == "" {
|
||||
a.logger.Error("Missing token in query parameter")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Missing token")
|
||||
}
|
||||
|
||||
claim, err := jwtutil.ParseJwt(tokenStr, a.JwtConfig.JwtAccessKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, jwtutil.ErrExpiredToken) {
|
||||
a.logger.Error("Token expired")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Token expired")
|
||||
}
|
||||
a.logger.Error("Invalid token", "error", err)
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid token")
|
||||
}
|
||||
|
||||
userID := claim.UserId
|
||||
if userID == 0 {
|
||||
a.logger.Error("Invalid user ID in token claims")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID")
|
||||
}
|
||||
|
||||
c.Locals("userID", userID)
|
||||
a.logger.Info("Authenticated WebSocket connection", "userID", userID)
|
||||
return c.Next()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"message": "Welcome to the FortuneBet API",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0dev2",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -81,6 +81,8 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
|
||||
a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile)
|
||||
a.fiber.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
|
||||
a.fiber.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
|
||||
a.fiber.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
|
||||
|
||||
a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
|
||||
a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
|
||||
|
|
@ -92,13 +94,17 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
|
||||
|
||||
a.fiber.Get("/cashiers", a.authMiddleware, h.GetAllCashiers)
|
||||
a.fiber.Get("/cashiers/:id", a.authMiddleware, h.GetCashierByID)
|
||||
a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier)
|
||||
a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier)
|
||||
|
||||
a.fiber.Get("/admin", a.authMiddleware, h.GetAllAdmins)
|
||||
a.fiber.Get("/admin/:id", a.authMiddleware, h.GetAdminByID)
|
||||
a.fiber.Post("/admin", a.authMiddleware, h.CreateAdmin)
|
||||
a.fiber.Put("/admin/:id", a.authMiddleware, h.UpdateAdmin)
|
||||
|
||||
a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers)
|
||||
a.fiber.Get("/managers/:id", a.authMiddleware, h.GetManagerByID)
|
||||
a.fiber.Post("/managers", a.authMiddleware, h.CreateManager)
|
||||
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
|
||||
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
|
||||
|
|
@ -124,12 +130,14 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Get("/search/branch", a.authMiddleware, h.SearchBranch)
|
||||
// /branch/search
|
||||
// branch/wallet
|
||||
a.fiber.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers)
|
||||
|
||||
// Branch Operation
|
||||
a.fiber.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations)
|
||||
a.fiber.Post("/supportedOperation", a.authMiddleware, h.CreateSupportedOperation)
|
||||
a.fiber.Post("/operation", a.authMiddleware, h.CreateBranchOperation)
|
||||
a.fiber.Get("/branch/:id/operation", a.authMiddleware, h.GetBranchOperations)
|
||||
|
||||
a.fiber.Delete("/branch/:id/operation/:opID", a.authMiddleware, h.DeleteBranchOperation)
|
||||
|
||||
// Company
|
||||
|
|
@ -149,11 +157,13 @@ func (a *App) initAppRoutes() {
|
|||
// Bet Routes
|
||||
a.fiber.Post("/bet", a.authMiddleware, h.CreateBet)
|
||||
a.fiber.Get("/bet", a.authMiddleware, h.GetAllBet)
|
||||
a.fiber.Get("/bet/:id", a.authMiddleware, h.GetBetByID)
|
||||
a.fiber.Get("/bet/:id", h.GetBetByID)
|
||||
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)
|
||||
a.fiber.Patch("/bet/:id", a.authMiddleware, h.UpdateCashOut)
|
||||
a.fiber.Delete("/bet/:id", a.authMiddleware, h.DeleteBet)
|
||||
|
||||
a.fiber.Post("/random/bet", a.authMiddleware, h.RandomBet)
|
||||
|
||||
// Wallet
|
||||
a.fiber.Get("/wallet", h.GetAllWallets)
|
||||
a.fiber.Get("/wallet/:id", h.GetWalletByID)
|
||||
|
|
@ -191,13 +201,17 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Put("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
|
||||
|
||||
// Notification Routes
|
||||
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)
|
||||
a.fiber.Post("/notifications/mark-as-read", h.MarkNotificationAsRead)
|
||||
a.fiber.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
||||
a.fiber.Get("/notifications", a.authMiddleware, h.GetNotifications)
|
||||
a.fiber.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
|
||||
a.fiber.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead)
|
||||
a.fiber.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications)
|
||||
a.fiber.Post("/notifications/create", h.CreateAndSendNotification)
|
||||
|
||||
// Virtual Game Routes
|
||||
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
|
||||
a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
|
||||
|
||||
}
|
||||
|
||||
///user/profile get
|
||||
|
|
|
|||
73
internal/web_server/ws/ws.go
Normal file
73
internal/web_server/ws/ws.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package ws
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Conn *websocket.Conn
|
||||
RecipientID int64
|
||||
}
|
||||
|
||||
type NotificationHub struct {
|
||||
Clients map[*Client]bool
|
||||
Broadcast chan interface{}
|
||||
Register chan *Client
|
||||
Unregister chan *Client
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewNotificationHub() *NotificationHub {
|
||||
return &NotificationHub{
|
||||
Clients: make(map[*Client]bool),
|
||||
Broadcast: make(chan interface{}, 1000),
|
||||
Register: make(chan *Client),
|
||||
Unregister: make(chan *Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHub) Run() {
|
||||
for {
|
||||
select {
|
||||
case client := <-h.Register:
|
||||
h.mu.Lock()
|
||||
h.Clients[client] = true
|
||||
h.mu.Unlock()
|
||||
log.Printf("Client registered: %d", client.RecipientID)
|
||||
case client := <-h.Unregister:
|
||||
h.mu.Lock()
|
||||
if _, ok := h.Clients[client]; ok {
|
||||
delete(h.Clients, client)
|
||||
client.Conn.Close()
|
||||
}
|
||||
h.mu.Unlock()
|
||||
log.Printf("Client unregistered: %d", client.RecipientID)
|
||||
case message := <-h.Broadcast:
|
||||
h.mu.Lock()
|
||||
for client := range h.Clients {
|
||||
if payload, ok := message.(map[string]interface{}); ok {
|
||||
if recipient, ok := payload["recipient_id"].(int64); ok && recipient == client.RecipientID {
|
||||
err := client.Conn.WriteJSON(payload)
|
||||
if err != nil {
|
||||
delete(h.Clients, client)
|
||||
client.Conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
39
makefile
39
makefile
|
|
@ -1,41 +1,58 @@
|
|||
include .env
|
||||
.PHONY: test
|
||||
test:
|
||||
@go test ./app
|
||||
@docker compose up -d test
|
||||
@docker compose exec test go test ./...
|
||||
@docker compose stop test
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
@mkdir -p coverage
|
||||
@go test -coverprofile=coverage.out ./internal/...
|
||||
@go tool cover -func=coverage.out -o coverage/coverage.txt
|
||||
@docker compose up -d test
|
||||
@docker compose exec test sh -c "go test -coverprofile=coverage.out ./internal/... && go tool cover -func=coverage.out -o coverage/coverage.txt"
|
||||
@docker cp $(shell docker ps -q -f "name=fortunebet-test-1"):/app/coverage ./ || true
|
||||
@docker compose stop test
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
@go build -ldflags="-s" -o ./bin/web ./cmd/main.go
|
||||
@docker compose build app
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
@echo "Running Go application"
|
||||
@go run ./cmd/main.go
|
||||
@docker compose up -d
|
||||
|
||||
.PHONY: stop
|
||||
stop:
|
||||
@docker compose down
|
||||
|
||||
.PHONY: air
|
||||
air:
|
||||
@echo "Running air"
|
||||
@echo "Running air locally (not in Docker)"
|
||||
@air -c .air.toml
|
||||
.PHONY: migrations/new
|
||||
.PHONY: migrations/up
|
||||
migrations/new:
|
||||
@echo 'Creating migration files for DB_URL'
|
||||
@migrate create -seq -ext=.sql -dir=./db/migrations $(name)
|
||||
|
||||
.PHONY: migrations/up
|
||||
migrations/up:
|
||||
@echo 'Running up migrations...'
|
||||
@migrate -path ./db/migrations -database $(DB_URL) up
|
||||
@docker compose up migrate
|
||||
|
||||
.PHONY: swagger
|
||||
swagger:
|
||||
@swag init -g cmd/main.go
|
||||
|
||||
.PHONY: db-up
|
||||
db-up:
|
||||
docker compose -f compose.db.yaml up
|
||||
@docker compose up -d postgres migrate
|
||||
|
||||
.PHONY: db-down
|
||||
db-down:
|
||||
docker compose -f compose.db.yaml down
|
||||
@docker compose down
|
||||
postgres:
|
||||
@docker exec -it fortunebet-backend-postgres-1 psql -U root -d gh
|
||||
|
||||
.PHONY: sqlc-gen
|
||||
sqlc-gen:
|
||||
@sqlc generate
|
||||
Loading…
Reference in New Issue
Block a user