integration issues

This commit is contained in:
Samuel Tariku 2025-05-16 22:38:10 +03:00
parent 95fb33c9d4
commit 4c6fb73342
30 changed files with 1213 additions and 202 deletions

View File

@ -234,12 +234,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 +295,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;
@ -344,7 +349,7 @@ VALUES (
'Test',
'Admin',
'test.admin@gmail.com',
'0911111111',
'0988554466',
crypt('password123', gen_salt('bf'))::bytea,
'admin',
TRUE,
@ -400,7 +405,7 @@ VALUES (
'Kirubel',
'Kibru',
'kirubeljkl679 @gmail.com',
'0911111111',
'0911554486',
crypt('password@123', gen_salt('bf'))::bytea,
'super_admin',
TRUE,
@ -412,8 +417,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,
@ -434,3 +438,53 @@ VALUES (
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
);

View File

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

View File

@ -805,6 +805,53 @@ const docTemplate = `{
}
}
},
"/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"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/branch/{id}/operation": {
"get": {
"description": "Gets branch operations",
@ -2756,6 +2803,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": [
@ -3052,7 +3143,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
"$ref": "#/definitions/handlers.UserProfileRes"
}
},
"400": {
@ -3076,6 +3167,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": [
@ -3601,9 +3738,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"
},
@ -3612,7 +3751,8 @@ const docTemplate = `{
"OUTCOME_STATUS_WIN",
"OUTCOME_STATUS_LOSS",
"OUTCOME_STATUS_VOID",
"OUTCOME_STATUS_HALF"
"OUTCOME_STATUS_HALF",
"OUTCOME_STATUS_ERROR"
]
},
"domain.PaymentOption": {
@ -3660,10 +3800,18 @@ 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
}
}
},
@ -4383,6 +4531,50 @@ const docTemplate = `{
}
}
},
"handlers.GetCashierRes": {
"type": "object",
"properties": {
"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": {
@ -4733,6 +4925,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": [

View File

@ -797,6 +797,53 @@
}
}
},
"/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"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/branch/{id}/operation": {
"get": {
"description": "Gets branch operations",
@ -2748,6 +2795,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": [
@ -3044,7 +3135,7 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
"$ref": "#/definitions/handlers.UserProfileRes"
}
},
"400": {
@ -3068,6 +3159,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": [
@ -3593,9 +3730,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"
},
@ -3604,7 +3743,8 @@
"OUTCOME_STATUS_WIN",
"OUTCOME_STATUS_LOSS",
"OUTCOME_STATUS_VOID",
"OUTCOME_STATUS_HALF"
"OUTCOME_STATUS_HALF",
"OUTCOME_STATUS_ERROR"
]
},
"domain.PaymentOption": {
@ -3652,10 +3792,18 @@
},
"domain.RandomBetReq": {
"type": "object",
"required": [
"branch_id",
"number_of_bets"
],
"properties": {
"branch_id": {
"type": "integer",
"example": 1
},
"number_of_bets": {
"type": "integer",
"example": 1
}
}
},
@ -4375,6 +4523,50 @@
}
}
},
"handlers.GetCashierRes": {
"type": "object",
"properties": {
"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": {
@ -4725,6 +4917,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": [

View File

@ -165,8 +165,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:
@ -175,6 +177,7 @@ definitions:
- OUTCOME_STATUS_LOSS
- OUTCOME_STATUS_VOID
- OUTCOME_STATUS_HALF
- OUTCOME_STATUS_ERROR
domain.PaymentOption:
enum:
- 0
@ -211,6 +214,12 @@ definitions:
branch_id:
example: 1
type: integer
number_of_bets:
example: 1
type: integer
required:
- branch_id
- number_of_bets
type: object
domain.RawOddsByMarketID:
properties:
@ -718,6 +727,35 @@ definitions:
static_updated_at:
type: string
type: object
handlers.GetCashierRes:
properties:
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:
@ -963,6 +1001,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:
@ -1658,6 +1715,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:
@ -2940,6 +3028,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:
@ -3132,7 +3249,7 @@ paths:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
$ref: '#/definitions/handlers.UserProfileRes'
"400":
description: Bad Request
schema:
@ -3148,6 +3265,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:

View File

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

View File

@ -146,13 +146,16 @@ type BranchOperation struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type CompaniesWithWallet struct {
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 {

View File

@ -98,6 +98,7 @@ type CreateBetReq struct {
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 {

View File

@ -14,6 +14,9 @@ type GetCompany struct {
ID int64
Name string
AdminID int64
AdminFirstName string
AdminLastName string
AdminPhoneNumber string
WalletID int64
WalletBalance Currency
IsWalletActive bool

View File

@ -26,11 +26,14 @@ 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
10040795, //EuroLeague
}

View File

@ -64,3 +64,21 @@ func (o *OutcomeStatus) String() string {
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
)

View File

@ -43,6 +43,7 @@ type FootballResultResponse struct {
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"`
@ -94,7 +95,7 @@ type BasketballResultResponse struct {
Possession []string `json:"possession"`
SuccessAttempts []string `json:"success_attempts"`
TimeSpendInLead []string `json:"timespent_inlead"`
Timeuts []string `json:"time_outs"`
TimeOuts []string `json:"time_outs"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
@ -104,7 +105,7 @@ type BasketballResultResponse struct {
NumberOfPeriods string `json:"numberofperiods"`
PeriodLength string `json:"periodlength"`
StadiumData map[string]string `json:"stadium_data"`
Length string `json:"length"`
Length int `json:"length"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`
@ -142,7 +143,7 @@ type IceHockeyResultResponse struct {
NumberOfPeriods string `json:"numberofperiods"`
PeriodLength string `json:"periodlength"`
StadiumData map[string]string `json:"stadium_data"`
Length string `json:"length"`
Length int `json:"length"`
Round string `json:"round"`
} `json:"extra"`
Events []map[string]string `json:"events"`

View File

@ -17,6 +17,13 @@ const (
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"
)
@ -111,6 +118,13 @@ var SupportedMarkets = map[int64]bool{
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,

View File

@ -25,7 +25,7 @@ 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,
@ -33,6 +33,9 @@ func convertDBCompanyWithWallet(dbCompany dbgen.CompaniesWithWallet) domain.GetC
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) {

View File

@ -230,6 +230,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 {

View File

@ -362,7 +362,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
// TODO: Add the option of passing number of created events
var selectedUpcomingEvents []domain.UpcomingEvent
numEventsPerBet := random.Intn(4) + 1 //Eliminate the option of 0
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))
@ -371,7 +371,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
}
s.logger.Info("Generating random bet events", "selectedUpcomingEvents", len(selectedUpcomingEvents))
// s.logger.Info("Generating random bet events", "selectedUpcomingEvents", len(selectedUpcomingEvents))
// Get market and odds for that
var randomOdds []domain.CreateBetOutcome
@ -395,7 +395,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
return domain.CreateBetRes{}, ErrGenerateRandomOutcome
}
s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
// s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
var cashoutID string
@ -491,9 +491,9 @@ func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domai
status = betOutcome.Status
case domain.OUTCOME_STATUS_WIN:
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
status = domain.OUTCOME_STATUS_HALF
status = domain.OUTCOME_STATUS_LOSS
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
status = domain.OUTCOME_STATUS_VOID
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 {
@ -509,16 +509,18 @@ func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domai
} 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_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_LOSS ||
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
}

View File

@ -100,7 +100,7 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
// sportIDs := []int{1, 18, 17}
sportIDs := []int{18}
sportIDs := []int{18, 17}
for _, sportID := range sportIDs {
var totalPages int = 1
@ -142,6 +142,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
@ -164,7 +165,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
}
if !slices.Contains(domain.SupportedLeagues, leagueID) {
fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
skippedLeague = append(skippedLeague, ev.League.Name)
continue
}
@ -172,7 +173,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
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,
@ -188,6 +189,7 @@ 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
}
err = s.store.SaveUpcomingEvent(ctx, event)
@ -234,7 +236,7 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
return s.store.GetExpiredUpcomingEvents(ctx)
}
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){
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)
}

View File

@ -246,7 +246,7 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
var marketIDint int
err := json.Unmarshal(market.ID, &marketIDint)
if err != nil {
s.logger.Error("Invalid market id")
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
errs = append(errs, err)
}
}

View File

@ -85,6 +85,8 @@ func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.Outcom
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
@ -94,7 +96,7 @@ func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.Outcom
} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
return domain.OUTCOME_STATUS_LOSS, nil
} else if secondOutcome == domain.OUTCOME_STATUS_HALF {
return domain.OUTCOME_STATUS_HALF, nil
return domain.OUTCOME_STATUS_VOID, nil
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
return domain.OUTCOME_STATUS_HALF, nil
} else {
@ -107,14 +109,14 @@ func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.Outcom
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 {
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 {
@ -123,7 +125,7 @@ func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.Outcom
}
case domain.OUTCOME_STATUS_HALF:
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF {
return domain.OUTCOME_STATUS_HALF, nil
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 {
@ -139,6 +141,8 @@ func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.Outcom
}
// 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",
@ -178,27 +182,33 @@ func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away i
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
}
continue
}
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
if err != nil {
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 {
return domain.OUTCOME_STATUS_ERROR, err
}
continue
}
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, 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
}
@ -306,24 +316,60 @@ 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
}
@ -346,6 +392,34 @@ func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }
return domain.OUTCOME_STATUS_LOSS, nil
}
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
@ -715,6 +789,30 @@ func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{
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) {

View File

@ -34,8 +34,9 @@ func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger,
}
}
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
@ -72,7 +73,7 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
if outcome.Expires.After(time.Now()) {
isDeleted = false
s.logger.Info("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
s.logger.Warn("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
continue
}
@ -85,6 +86,10 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
// 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 {
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,
@ -123,13 +128,14 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
}
continue
}
fmt.Printf("🧾 Updating bet status for event %v (%d/%d) to %v\n", event.ID, j+1, len(outcomes), status.String())
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)
continue
}
fmt.Printf("✅ Successfully updated 🎫 Bet for event %v(%v) (%d/%d) \n",
fmt.Printf("✅ Successfully updated 🎫 Bet %v - event %v(%v) (%d/%d) \n",
outcome.BetID,
event.HomeTeam+" "+event.AwayTeam, event.ID,
j+1, len(outcomes))
@ -273,6 +279,34 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
}
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)
}
}
func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var fbResp domain.FootballResultResponse
if err := json.Unmarshal(resultRes, &fbResp); err != nil {
@ -280,16 +314,24 @@ 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 football outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
@ -309,12 +351,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)
@ -337,12 +384,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)
@ -388,7 +440,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)
@ -420,6 +475,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)
@ -456,7 +526,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)
@ -487,6 +559,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)

View File

@ -15,6 +15,7 @@ type UserStore interface {
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)

View File

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

View File

@ -21,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 * * * *",
// 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
// }
// 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 * * * *",
spec: "0 0 * * * *", // Every 1 hour
task: func() {
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
log.Printf("FetchUpcomingEvents 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 * * * *", // Every 15 Minutes
task: func() {
log.Println("Fetching results for upcoming events...")
@ -81,7 +52,6 @@ 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)
}

View File

@ -116,7 +116,10 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
res, err := h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
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 {
h.logger.Error("Random Bet failed", "error", err)
@ -126,7 +129,7 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
}
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
}
}
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
}
@ -177,8 +180,9 @@ 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 := domain.ConvertBet(bet)

View File

@ -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"
)
@ -492,6 +493,64 @@ 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

View File

@ -31,6 +31,9 @@ type GetCompanyRes struct {
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 {
@ -50,6 +53,9 @@ func convertGetCompany(company domain.GetCompany) GetCompanyRes {
WalletID: company.WalletID,
WalletBalance: company.WalletBalance.Float32(),
IsActive: company.IsWalletActive,
AdminFirstName: company.AdminFirstName,
AdminLastName: company.AdminLastName,
AdminPhoneNumber: company.AdminPhoneNumber,
}
}

View File

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

View File

@ -450,7 +450,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
@ -513,3 +513,75 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Cashiers 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)
}

View File

@ -35,7 +35,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",
})
})
@ -77,6 +77,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)
@ -120,12 +122,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
@ -145,7 +149,7 @@ 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)