Merge branch 'ticket-bet'

This commit is contained in:
Samuel Tariku 2025-04-28 20:57:41 +03:00
commit e98d75f0bb
47 changed files with 2030 additions and 459 deletions

View File

@ -10,8 +10,9 @@ CREATE TABLE IF NOT EXISTS users (
phone_verified BOOLEAN NOT NULL DEFAULT FALSE, phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, updated_at TIMESTAMPTZ,
-- company_id BIGINT,
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended suspended_at TIMESTAMPTZ NULL,
-- this can be NULL if the user is not suspended
suspended BOOLEAN NOT NULL DEFAULT FALSE, suspended BOOLEAN NOT NULL DEFAULT FALSE,
CHECK ( CHECK (
email IS NOT NULL email IS NOT NULL
@ -53,6 +54,7 @@ CREATE TABLE IF NOT EXISTS bets (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_shop_bet BOOLEAN NOT NULL, is_shop_bet BOOLEAN NOT NULL,
UNIQUE(cashout_id),
CHECK ( CHECK (
user_id IS NOT NULL user_id IS NOT NULL
OR branch_id IS NOT NULL OR branch_id IS NOT NULL
@ -148,6 +150,7 @@ CREATE TABLE IF NOT EXISTS transactions (
branch_id BIGINT NOT NULL, branch_id BIGINT NOT NULL,
cashier_id BIGINT NOT NULL, cashier_id BIGINT NOT NULL,
bet_id BIGINT NOT NULL, bet_id BIGINT NOT NULL,
number_of_outcomes BIGINT NOT NULL,
type BIGINT NOT NULL, type BIGINT NOT NULL,
payment_option BIGINT NOT NULL, payment_option BIGINT NOT NULL,
full_name VARCHAR(255) NOT NULL, full_name VARCHAR(255) NOT NULL,
@ -247,6 +250,12 @@ CREATE TABLE companies (
admin_id BIGINT NOT NULL, admin_id BIGINT NOT NULL,
wallet_id BIGINT NOT NULL wallet_id BIGINT NOT NULL
); );
CREATE VIEW companies_with_wallets AS
SELECT companies.*,
wallets.balance,
wallets.is_active
FROM companies
JOIN wallets ON wallets.id = companies.wallet_id;
ALTER TABLE refresh_tokens ALTER TABLE refresh_tokens
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE bets ALTER TABLE bets
@ -275,6 +284,9 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
ALTER TABLE branch_cashiers ALTER TABLE branch_cashiers
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id), 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_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
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);
----------------------------------------------seed data------------------------------------------------------------- ----------------------------------------------seed data-------------------------------------------------------------
-------------------------------------- DO NOT USE IN PRODUCTION------------------------------------------------- -------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@ -1,16 +1,21 @@
-- name: GetUserByEmailPhone :one -- name: GetUserByEmailPhone :one
SELECT * FROM users SELECT *
WHERE email = $1 OR phone_number = $2; FROM users
WHERE email = $1
OR phone_number = $2;
-- name: CreateRefreshToken :exec -- name: CreateRefreshToken :exec
INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked) INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked)
VALUES ($1, $2, $3, $4, $5); VALUES ($1, $2, $3, $4, $5);
-- name: GetRefreshToken :one -- name: GetRefreshToken :one
SELECT * FROM refresh_tokens SELECT *
FROM refresh_tokens
WHERE token = $1; WHERE token = $1;
-- name: GetRefreshTokenByUserID :one
SELECT *
FROM refresh_tokens
WHERE user_id = $1;
-- name: RevokeRefreshToken :exec -- name: RevokeRefreshToken :exec
UPDATE refresh_tokens UPDATE refresh_tokens
SET revoked = TRUE SET revoked = TRUE
WHERE token = $1; WHERE token = $1;

View File

@ -8,11 +8,15 @@ VALUES ($1, $2, $3)
RETURNING *; RETURNING *;
-- name: GetAllCompanies :many -- name: GetAllCompanies :many
SELECT * SELECT *
FROM companies; FROM companies_with_wallets;
-- name: GetCompanyByID :one -- name: GetCompanyByID :one
SELECT * SELECT *
FROM companies FROM companies_with_wallets
WHERE id = $1; WHERE id = $1;
-- name: SearchCompanyByName :many
SELECT *
FROM companies_with_wallets
WHERE name ILIKE '%' || $1 || '%';
-- name: UpdateCompany :one -- name: UpdateCompany :one
UPDATE companies UPDATE companies
SET name = $1, SET name = $1,

View File

@ -9,9 +9,24 @@ INSERT INTO users (
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
company_id
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id, RETURNING id,
first_name, first_name,
last_name, last_name,
@ -21,7 +36,9 @@ RETURNING id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at; updated_at,
suspended,
company_id;
-- name: GetUserByID :one -- name: GetUserByID :one
SELECT * SELECT *
FROM users FROM users
@ -36,8 +53,31 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
FROM users; suspended,
suspended_at,
company_id
FROM users
wHERE (
role = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
LIMIT $3 OFFSET $4;
-- name: GetTotalUsers :one
SELECT COUNT(*)
FROM users
wHERE (
role = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
);
-- name: SearchUserByNameOrPhone :many -- name: SearchUserByNameOrPhone :many
SELECT id, SELECT id,
first_name, first_name,
@ -48,7 +88,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE first_name ILIKE '%' || $1 || '%' WHERE first_name ILIKE '%' || $1 || '%'
OR last_name ILIKE '%' || $1 || '%' OR last_name ILIKE '%' || $1 || '%'
@ -62,6 +105,12 @@ SET first_name = $1,
role = $5, role = $5,
updated_at = $6 updated_at = $6
WHERE id = $7; WHERE id = $7;
-- name: SuspendUser :exec
UPDATE users
SET suspended = $1,
suspended_at = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $3;
-- name: DeleteUser :exec -- name: DeleteUser :exec
DELETE FROM users DELETE FROM users
WHERE id = $1; WHERE id = $1;
@ -88,7 +137,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE email = $1; WHERE email = $1;
-- name: GetUserByPhone :one -- name: GetUserByPhone :one
@ -101,7 +153,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE phone_number = $1; WHERE phone_number = $1;
-- name: UpdatePassword :exec -- name: UpdatePassword :exec

View File

@ -24,6 +24,111 @@ const docTemplate = `{
"host": "{{.Host}}", "host": "{{.Host}}",
"basePath": "{{.BasePath}}", "basePath": "{{.BasePath}}",
"paths": { "paths": {
"/admin": {
"get": {
"description": "Get all Admins",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Get all Admins",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"post": {
"description": "Create Admin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Create Admin",
"parameters": [
{
"description": "Create admin",
"name": "manger",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.CreateAdminReq"
}
}
],
"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"
}
}
}
}
},
"/auth/login": { "/auth/login": {
"post": { "post": {
"description": "Login customer", "description": "Login customer",
@ -955,6 +1060,13 @@ const docTemplate = `{
], ],
"summary": "Update cashier", "summary": "Update cashier",
"parameters": [ "parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{ {
"description": "Update cashier", "description": "Update cashier",
"name": "cashier", "name": "cashier",
@ -1361,7 +1473,7 @@ const docTemplate = `{
} }
}, },
"post": { "post": {
"description": "Create Managers", "description": "Create Manager",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1371,7 +1483,7 @@ const docTemplate = `{
"tags": [ "tags": [
"manager" "manager"
], ],
"summary": "Create Managers", "summary": "Create Manager",
"parameters": [ "parameters": [
{ {
"description": "Create manager", "description": "Create manager",
@ -1991,6 +2103,44 @@ const docTemplate = `{
} }
} }
}, },
"/search/company": {
"get": {
"description": "Gets all companies",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"company"
],
"summary": "Gets all companies",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.CompanyRes"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/supportedOperation": { "/supportedOperation": {
"get": { "get": {
"description": "Gets all supported operations", "description": "Gets all supported operations",
@ -2830,6 +2980,56 @@ const docTemplate = `{
} }
} }
}, },
"/user/single/{id}": {
"get": {
"description": "Get a single user by id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Get 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"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/wallet": { "/user/wallet": {
"get": { "get": {
"security": [ "security": [
@ -3651,6 +3851,9 @@ const docTemplate = `{
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3694,6 +3897,35 @@ const docTemplate = `{
} }
} }
}, },
"handlers.CreateAdminReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
},
"first_name": {
"type": "string",
"example": "John"
},
"last_name": {
"type": "string",
"example": "Doe"
},
"password": {
"type": "string",
"example": "password123"
},
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3722,10 +3954,6 @@ const docTemplate = `{
"type": "string", "type": "string",
"example": "John" "example": "John"
}, },
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": { "outcomes": {
"type": "array", "type": "array",
"items": { "items": {
@ -3820,6 +4048,10 @@ const docTemplate = `{
"phone_number": { "phone_number": {
"type": "string", "type": "string",
"example": "1234567890" "example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
} }
} }
}, },
@ -3839,6 +4071,10 @@ const docTemplate = `{
"handlers.CreateManagerReq": { "handlers.CreateManagerReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": { "email": {
"type": "string", "type": "string",
"example": "john.doe@example.com" "example": "john.doe@example.com"
@ -4060,7 +4296,6 @@ const docTemplate = `{
"example": "Doe" "example": "Doe"
}, },
"otp": { "otp": {
"description": "Role string",
"type": "string", "type": "string",
"example": "123456" "example": "123456"
}, },
@ -4093,18 +4328,27 @@ const docTemplate = `{
}, },
"handlers.ResetPasswordReq": { "handlers.ResetPasswordReq": {
"type": "object", "type": "object",
"required": [
"otp",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string" "type": "string",
"example": "john.doe@example.com"
}, },
"otp": { "otp": {
"type": "string" "type": "string",
"example": "123456"
}, },
"password": { "password": {
"type": "string" "type": "string",
"minLength": 8,
"example": "newpassword123"
}, },
"phoneNumber": { "phone_number": {
"type": "string" "type": "string",
"example": "1234567890"
} }
} }
}, },
@ -4195,6 +4439,10 @@ const docTemplate = `{
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": { "payment_option": {
"allOf": [ "allOf": [
{ {
@ -4315,6 +4563,9 @@ const docTemplate = `{
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"last_login": {
"type": "string"
},
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },

View File

@ -16,6 +16,111 @@
"version": "1.0" "version": "1.0"
}, },
"paths": { "paths": {
"/admin": {
"get": {
"description": "Get all Admins",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Get all Admins",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"post": {
"description": "Create Admin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Create Admin",
"parameters": [
{
"description": "Create admin",
"name": "manger",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.CreateAdminReq"
}
}
],
"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"
}
}
}
}
},
"/auth/login": { "/auth/login": {
"post": { "post": {
"description": "Login customer", "description": "Login customer",
@ -947,6 +1052,13 @@
], ],
"summary": "Update cashier", "summary": "Update cashier",
"parameters": [ "parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{ {
"description": "Update cashier", "description": "Update cashier",
"name": "cashier", "name": "cashier",
@ -1353,7 +1465,7 @@
} }
}, },
"post": { "post": {
"description": "Create Managers", "description": "Create Manager",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1363,7 +1475,7 @@
"tags": [ "tags": [
"manager" "manager"
], ],
"summary": "Create Managers", "summary": "Create Manager",
"parameters": [ "parameters": [
{ {
"description": "Create manager", "description": "Create manager",
@ -1983,6 +2095,44 @@
} }
} }
}, },
"/search/company": {
"get": {
"description": "Gets all companies",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"company"
],
"summary": "Gets all companies",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.CompanyRes"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/supportedOperation": { "/supportedOperation": {
"get": { "get": {
"description": "Gets all supported operations", "description": "Gets all supported operations",
@ -2822,6 +2972,56 @@
} }
} }
}, },
"/user/single/{id}": {
"get": {
"description": "Get a single user by id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Get 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"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/wallet": { "/user/wallet": {
"get": { "get": {
"security": [ "security": [
@ -3643,6 +3843,9 @@
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3686,6 +3889,35 @@
} }
} }
}, },
"handlers.CreateAdminReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
},
"first_name": {
"type": "string",
"example": "John"
},
"last_name": {
"type": "string",
"example": "Doe"
},
"password": {
"type": "string",
"example": "password123"
},
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3714,10 +3946,6 @@
"type": "string", "type": "string",
"example": "John" "example": "John"
}, },
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": { "outcomes": {
"type": "array", "type": "array",
"items": { "items": {
@ -3812,6 +4040,10 @@
"phone_number": { "phone_number": {
"type": "string", "type": "string",
"example": "1234567890" "example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
} }
} }
}, },
@ -3831,6 +4063,10 @@
"handlers.CreateManagerReq": { "handlers.CreateManagerReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": { "email": {
"type": "string", "type": "string",
"example": "john.doe@example.com" "example": "john.doe@example.com"
@ -4052,7 +4288,6 @@
"example": "Doe" "example": "Doe"
}, },
"otp": { "otp": {
"description": "Role string",
"type": "string", "type": "string",
"example": "123456" "example": "123456"
}, },
@ -4085,18 +4320,27 @@
}, },
"handlers.ResetPasswordReq": { "handlers.ResetPasswordReq": {
"type": "object", "type": "object",
"required": [
"otp",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string" "type": "string",
"example": "john.doe@example.com"
}, },
"otp": { "otp": {
"type": "string" "type": "string",
"example": "123456"
}, },
"password": { "password": {
"type": "string" "type": "string",
"minLength": 8,
"example": "newpassword123"
}, },
"phoneNumber": { "phone_number": {
"type": "string" "type": "string",
"example": "1234567890"
} }
} }
}, },
@ -4187,6 +4431,10 @@
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": { "payment_option": {
"allOf": [ "allOf": [
{ {
@ -4307,6 +4555,9 @@
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"last_login": {
"type": "string"
},
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },

View File

@ -384,6 +384,8 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
required:
- phone_number
type: object type: object
handlers.CheckPhoneEmailExistRes: handlers.CheckPhoneEmailExistRes:
properties: properties:
@ -407,6 +409,27 @@ definitions:
example: 1 example: 1
type: integer type: integer
type: object type: object
handlers.CreateAdminReq:
properties:
company_id:
example: 1
type: integer
email:
example: john.doe@example.com
type: string
first_name:
example: John
type: string
last_name:
example: Doe
type: string
password:
example: password123
type: string
phone_number:
example: "1234567890"
type: string
type: object
handlers.CreateBetOutcomeReq: handlers.CreateBetOutcomeReq:
properties: properties:
event_id: event_id:
@ -427,9 +450,6 @@ definitions:
full_name: full_name:
example: John example: John
type: string type: string
is_shop_bet:
example: false
type: boolean
outcomes: outcomes:
items: items:
$ref: '#/definitions/handlers.CreateBetOutcomeReq' $ref: '#/definitions/handlers.CreateBetOutcomeReq'
@ -496,6 +516,9 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
suspended:
example: false
type: boolean
type: object type: object
handlers.CreateCompanyReq: handlers.CreateCompanyReq:
properties: properties:
@ -508,6 +531,9 @@ definitions:
type: object type: object
handlers.CreateManagerReq: handlers.CreateManagerReq:
properties: properties:
company_id:
example: 1
type: integer
email: email:
example: john.doe@example.com example: john.doe@example.com
type: string type: string
@ -663,7 +689,6 @@ definitions:
example: Doe example: Doe
type: string type: string
otp: otp:
description: Role string
example: "123456" example: "123456"
type: string type: string
password: password:
@ -688,13 +713,21 @@ definitions:
handlers.ResetPasswordReq: handlers.ResetPasswordReq:
properties: properties:
email: email:
example: john.doe@example.com
type: string type: string
otp: otp:
example: "123456"
type: string type: string
password: password:
example: newpassword123
minLength: 8
type: string type: string
phoneNumber: phone_number:
example: "1234567890"
type: string type: string
required:
- otp
- password
type: object type: object
handlers.SearchUserByNameOrPhoneReq: handlers.SearchUserByNameOrPhoneReq:
properties: properties:
@ -757,6 +790,9 @@ definitions:
id: id:
example: 1 example: 1
type: integer type: integer
number_of_outcomes:
example: 1
type: integer
payment_option: payment_option:
allOf: allOf:
- $ref: '#/definitions/domain.PaymentOption' - $ref: '#/definitions/domain.PaymentOption'
@ -839,6 +875,8 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
last_login:
type: string
last_name: last_name:
type: string type: string
phone_number: phone_number:
@ -997,6 +1035,75 @@ info:
title: FortuneBet API title: FortuneBet API
version: "1.0" version: "1.0"
paths: paths:
/admin:
get:
consumes:
- application/json
description: Get all Admins
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get all Admins
tags:
- admin
post:
consumes:
- application/json
description: Create Admin
parameters:
- description: Create admin
in: body
name: manger
required: true
schema:
$ref: '#/definitions/handlers.CreateAdminReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Create Admin
tags:
- admin
/auth/login: /auth/login:
post: post:
consumes: consumes:
@ -1608,6 +1715,11 @@ paths:
- application/json - application/json
description: Update cashier description: Update cashier
parameters: parameters:
- description: Cashier ID
in: path
name: id
required: true
type: integer
- description: Update cashier - description: Update cashier
in: body in: body
name: cashier name: cashier
@ -1882,7 +1994,7 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Create Managers description: Create Manager
parameters: parameters:
- description: Create manager - description: Create manager
in: body in: body
@ -1909,7 +2021,7 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.APIResponse' $ref: '#/definitions/response.APIResponse'
summary: Create Managers summary: Create Manager
tags: tags:
- manager - manager
/managers/{id}: /managers/{id}:
@ -2291,6 +2403,31 @@ paths:
summary: Search branches summary: Search branches
tags: tags:
- branch - branch
/search/company:
get:
consumes:
- application/json
description: Gets all companies
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/handlers.CompanyRes'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Gets all companies
tags:
- company
/supportedOperation: /supportedOperation:
get: get:
consumes: consumes:
@ -2840,6 +2977,39 @@ paths:
summary: Send reset code summary: Send reset code
tags: tags:
- user - user
/user/single/{id}:
get:
consumes:
- application/json
description: Get a single user by 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'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get user by id
tags:
- user
/user/wallet: /user/wallet:
get: get:
consumes: consumes:

View File

@ -36,7 +36,8 @@ func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshToken
} }
const GetRefreshToken = `-- name: GetRefreshToken :one const GetRefreshToken = `-- name: GetRefreshToken :one
SELECT id, user_id, token, expires_at, created_at, revoked FROM refresh_tokens SELECT id, user_id, token, expires_at, created_at, revoked
FROM refresh_tokens
WHERE token = $1 WHERE token = $1
` `
@ -54,9 +55,31 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token string) (RefreshTok
return i, err return i, err
} }
const GetRefreshTokenByUserID = `-- name: GetRefreshTokenByUserID :one
SELECT id, user_id, token, expires_at, created_at, revoked
FROM refresh_tokens
WHERE user_id = $1
`
func (q *Queries) GetRefreshTokenByUserID(ctx context.Context, userID int64) (RefreshToken, error) {
row := q.db.QueryRow(ctx, GetRefreshTokenByUserID, userID)
var i RefreshToken
err := row.Scan(
&i.ID,
&i.UserID,
&i.Token,
&i.ExpiresAt,
&i.CreatedAt,
&i.Revoked,
)
return i, err
}
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended, referral_code, referred_by FROM users SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended, referral_code, referred_by
WHERE email = $1 OR phone_number = $2 FROM users
WHERE email = $1
OR phone_number = $2
` `
type GetUserByEmailPhoneParams struct { type GetUserByEmailPhoneParams struct {
@ -79,6 +102,7 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt, &i.SuspendedAt,
&i.Suspended, &i.Suspended,
&i.ReferralCode, &i.ReferralCode,
@ -88,8 +112,8 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
} }
const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
UPDATE refresh_tokens UPDATE refresh_tokens
SET revoked = TRUE SET revoked = TRUE
WHERE token = $1 WHERE token = $1
` `

View File

@ -191,7 +191,7 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
} }
const GetAllCashiers = `-- name: GetAllCashiers :many const GetAllCashiers = `-- name: GetAllCashiers :many
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended, users.referral_code, users.referred_by 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 FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id JOIN users ON branch_cashiers.user_id = users.id
` `
@ -217,6 +217,7 @@ func (q *Queries) GetAllCashiers(ctx context.Context) ([]User, error) {
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt, &i.SuspendedAt,
&i.Suspended, &i.Suspended,
&i.ReferralCode, &i.ReferralCode,
@ -430,7 +431,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
} }
const GetCashiersByBranch = `-- name: GetCashiersByBranch :many const GetCashiersByBranch = `-- name: GetCashiersByBranch :many
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.suspended_at, users.suspended, users.referral_code, users.referred_by 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 FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id JOIN users ON branch_cashiers.user_id = users.id
WHERE branch_cashiers.branch_id = $1 WHERE branch_cashiers.branch_id = $1
@ -457,6 +458,7 @@ func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]Us
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt, &i.SuspendedAt,
&i.Suspended, &i.Suspended,
&i.ReferralCode, &i.ReferralCode,

View File

@ -7,6 +7,8 @@ package dbgen
import ( import (
"context" "context"
"github.com/jackc/pgx/v5/pgtype"
) )
const CreateCompany = `-- name: CreateCompany :one const CreateCompany = `-- name: CreateCompany :one
@ -48,24 +50,26 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
} }
const GetAllCompanies = `-- name: GetAllCompanies :many const GetAllCompanies = `-- name: GetAllCompanies :many
SELECT id, name, admin_id, wallet_id SELECT id, name, admin_id, wallet_id, balance, is_active
FROM companies FROM companies_with_wallets
` `
func (q *Queries) GetAllCompanies(ctx context.Context) ([]Company, error) { func (q *Queries) GetAllCompanies(ctx context.Context) ([]CompaniesWithWallet, error) {
rows, err := q.db.Query(ctx, GetAllCompanies) rows, err := q.db.Query(ctx, GetAllCompanies)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []Company var items []CompaniesWithWallet
for rows.Next() { for rows.Next() {
var i Company var i CompaniesWithWallet
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.Balance,
&i.IsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -78,23 +82,58 @@ func (q *Queries) GetAllCompanies(ctx context.Context) ([]Company, error) {
} }
const GetCompanyByID = `-- name: GetCompanyByID :one const GetCompanyByID = `-- name: GetCompanyByID :one
SELECT id, name, admin_id, wallet_id SELECT id, name, admin_id, wallet_id, balance, is_active
FROM companies FROM companies_with_wallets
WHERE id = $1 WHERE id = $1
` `
func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (Company, error) { func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesWithWallet, error) {
row := q.db.QueryRow(ctx, GetCompanyByID, id) row := q.db.QueryRow(ctx, GetCompanyByID, id)
var i Company var i CompaniesWithWallet
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.AdminID, &i.AdminID,
&i.WalletID, &i.WalletID,
&i.Balance,
&i.IsActive,
) )
return i, err return i, err
} }
const SearchCompanyByName = `-- name: SearchCompanyByName :many
SELECT id, name, admin_id, wallet_id, balance, is_active
FROM companies_with_wallets
WHERE name ILIKE '%' || $1 || '%'
`
func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) ([]CompaniesWithWallet, error) {
rows, err := q.db.Query(ctx, SearchCompanyByName, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CompaniesWithWallet
for rows.Next() {
var i CompaniesWithWallet
if err := rows.Scan(
&i.ID,
&i.Name,
&i.AdminID,
&i.WalletID,
&i.Balance,
&i.IsActive,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const UpdateCompany = `-- name: UpdateCompany :one const UpdateCompany = `-- name: UpdateCompany :one
UPDATE companies UPDATE companies
SET name = $1, SET name = $1,

View File

@ -145,6 +145,15 @@ type BranchOperation struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` 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 Company struct { type Company struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -325,23 +334,24 @@ type TicketWithOutcome struct {
} }
type Transaction struct { type Transaction struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
CashierID int64 `json:"cashier_id"` CashierID int64 `json:"cashier_id"`
BetID int64 `json:"bet_id"` BetID int64 `json:"bet_id"`
Type int64 `json:"type"` NumberOfOutcomes int64 `json:"number_of_outcomes"`
PaymentOption int64 `json:"payment_option"` Type int64 `json:"type"`
FullName string `json:"full_name"` PaymentOption int64 `json:"payment_option"`
PhoneNumber string `json:"phone_number"` FullName string `json:"full_name"`
BankCode string `json:"bank_code"` PhoneNumber string `json:"phone_number"`
BeneficiaryName string `json:"beneficiary_name"` BankCode string `json:"bank_code"`
AccountName string `json:"account_name"` BeneficiaryName string `json:"beneficiary_name"`
AccountNumber string `json:"account_number"` AccountName string `json:"account_name"`
ReferenceNumber string `json:"reference_number"` AccountNumber string `json:"account_number"`
Verified bool `json:"verified"` ReferenceNumber string `json:"reference_number"`
CreatedAt pgtype.Timestamp `json:"created_at"` Verified bool `json:"verified"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type User struct { type User struct {
@ -356,6 +366,7 @@ type User struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
CompanyID pgtype.Int8 `json:"company_id"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"` SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
ReferralCode pgtype.Text `json:"referral_code"` ReferralCode pgtype.Text `json:"referral_code"`

View File

@ -10,7 +10,7 @@ import (
) )
const CreateTransaction = `-- name: CreateTransaction :one const CreateTransaction = `-- name: CreateTransaction :one
INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at
` `
type CreateTransactionParams struct { type CreateTransactionParams struct {
@ -52,6 +52,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
&i.BranchID, &i.BranchID,
&i.CashierID, &i.CashierID,
&i.BetID, &i.BetID,
&i.NumberOfOutcomes,
&i.Type, &i.Type,
&i.PaymentOption, &i.PaymentOption,
&i.FullName, &i.FullName,
@ -69,7 +70,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
} }
const GetAllTransactions = `-- name: GetAllTransactions :many const GetAllTransactions = `-- name: GetAllTransactions :many
SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions
` `
func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) { func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) {
@ -87,6 +88,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
&i.BranchID, &i.BranchID,
&i.CashierID, &i.CashierID,
&i.BetID, &i.BetID,
&i.NumberOfOutcomes,
&i.Type, &i.Type,
&i.PaymentOption, &i.PaymentOption,
&i.FullName, &i.FullName,
@ -111,7 +113,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
} }
const GetTransactionByBranch = `-- name: GetTransactionByBranch :many const GetTransactionByBranch = `-- name: GetTransactionByBranch :many
SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE branch_id = $1 SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE branch_id = $1
` `
func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) { func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) {
@ -129,6 +131,7 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
&i.BranchID, &i.BranchID,
&i.CashierID, &i.CashierID,
&i.BetID, &i.BetID,
&i.NumberOfOutcomes,
&i.Type, &i.Type,
&i.PaymentOption, &i.PaymentOption,
&i.FullName, &i.FullName,
@ -153,7 +156,7 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
} }
const GetTransactionByID = `-- name: GetTransactionByID :one const GetTransactionByID = `-- name: GetTransactionByID :one
SELECT id, amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1
` `
func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) {
@ -165,6 +168,7 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction
&i.BranchID, &i.BranchID,
&i.CashierID, &i.CashierID,
&i.BetID, &i.BetID,
&i.NumberOfOutcomes,
&i.Type, &i.Type,
&i.PaymentOption, &i.PaymentOption,
&i.FullName, &i.FullName,

View File

@ -54,9 +54,24 @@ INSERT INTO users (
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
company_id
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id, RETURNING id,
first_name, first_name,
last_name, last_name,
@ -66,7 +81,9 @@ RETURNING id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
company_id
` `
type CreateUserParams struct { type CreateUserParams struct {
@ -80,6 +97,8 @@ type CreateUserParams struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
type CreateUserRow struct { type CreateUserRow struct {
@ -93,6 +112,8 @@ type CreateUserRow struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) { func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
@ -107,6 +128,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
arg.PhoneVerified, arg.PhoneVerified,
arg.CreatedAt, arg.CreatedAt,
arg.UpdatedAt, arg.UpdatedAt,
arg.Suspended,
arg.CompanyID,
) )
var i CreateUserRow var i CreateUserRow
err := row.Scan( err := row.Scan(
@ -120,6 +143,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Suspended,
&i.CompanyID,
) )
return i, err return i, err
} }
@ -144,10 +169,29 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
wHERE (
role = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
LIMIT $3 OFFSET $4
` `
type GetAllUsersParams struct {
Role string `json:"role"`
CompanyID pgtype.Int8 `json:"company_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
type GetAllUsersRow struct { type GetAllUsersRow struct {
ID int64 `json:"id"` ID int64 `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
@ -159,10 +203,18 @@ type GetAllUsersRow struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) { func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]GetAllUsersRow, error) {
rows, err := q.db.Query(ctx, GetAllUsers) rows, err := q.db.Query(ctx, GetAllUsers,
arg.Role,
arg.CompanyID,
arg.Limit,
arg.Offset,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,6 +233,9 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) {
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Suspended,
&i.SuspendedAt,
&i.CompanyID,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -192,6 +247,31 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) {
return items, nil return items, nil
} }
const GetTotalUsers = `-- name: GetTotalUsers :one
SELECT COUNT(*)
FROM users
wHERE (
role = $1
OR $1 IS NULL
)
AND (
company_id = $2
OR $2 IS NULL
)
`
type GetTotalUsersParams struct {
Role string `json:"role"`
CompanyID pgtype.Int8 `json:"company_id"`
}
func (q *Queries) GetTotalUsers(ctx context.Context, arg GetTotalUsersParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalUsers, arg.Role, arg.CompanyID)
var count int64
err := row.Scan(&count)
return count, err
}
const GetUserByEmail = `-- name: GetUserByEmail :one const GetUserByEmail = `-- name: GetUserByEmail :one
SELECT id, SELECT id,
first_name, first_name,
@ -202,7 +282,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE email = $1 WHERE email = $1
` `
@ -218,6 +301,9 @@ type GetUserByEmailRow struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error) { func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error) {
@ -234,12 +320,15 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUse
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Suspended,
&i.SuspendedAt,
&i.CompanyID,
) )
return i, err return i, err
} }
const GetUserByID = `-- name: GetUserByID :one const GetUserByID = `-- name: GetUserByID :one
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended, referral_code, referred_by SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended, referral_code, referred_by
FROM users FROM users
WHERE id = $1 WHERE id = $1
` `
@ -259,6 +348,7 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt, &i.SuspendedAt,
&i.Suspended, &i.Suspended,
&i.ReferralCode, &i.ReferralCode,
@ -277,7 +367,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE phone_number = $1 WHERE phone_number = $1
` `
@ -293,6 +386,9 @@ type GetUserByPhoneRow struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) (GetUserByPhoneRow, error) { func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) (GetUserByPhoneRow, error) {
@ -309,6 +405,9 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) (
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Suspended,
&i.SuspendedAt,
&i.CompanyID,
) )
return i, err return i, err
} }
@ -323,7 +422,10 @@ SELECT id,
email_verified, email_verified,
phone_verified, phone_verified,
created_at, created_at,
updated_at updated_at,
suspended,
suspended_at,
company_id
FROM users FROM users
WHERE first_name ILIKE '%' || $1 || '%' WHERE first_name ILIKE '%' || $1 || '%'
OR last_name ILIKE '%' || $1 || '%' OR last_name ILIKE '%' || $1 || '%'
@ -341,6 +443,9 @@ type SearchUserByNameOrPhoneRow struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
CompanyID pgtype.Int8 `json:"company_id"`
} }
func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.Text) ([]SearchUserByNameOrPhoneRow, error) { func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.Text) ([]SearchUserByNameOrPhoneRow, error) {
@ -363,6 +468,9 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.T
&i.PhoneVerified, &i.PhoneVerified,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Suspended,
&i.SuspendedAt,
&i.CompanyID,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -374,6 +482,25 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, dollar_1 pgtype.T
return items, nil return items, nil
} }
const SuspendUser = `-- name: SuspendUser :exec
UPDATE users
SET suspended = $1,
suspended_at = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $3
`
type SuspendUserParams struct {
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
ID int64 `json:"id"`
}
func (q *Queries) SuspendUser(ctx context.Context, arg SuspendUserParams) error {
_, err := q.db.Exec(ctx, SuspendUser, arg.Suspended, arg.SuspendedAt, arg.ID)
return err
}
const UpdatePassword = `-- name: UpdatePassword :exec const UpdatePassword = `-- name: UpdatePassword :exec
UPDATE users UPDATE users
SET password = $1, SET password = $1,

5
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/fasthttp/websocket v1.5.3 // indirect github.com/fasthttp/websocket v1.5.8 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
@ -31,6 +31,7 @@ require (
github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gofiber/contrib/websocket v1.3.4
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
@ -44,7 +45,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect

6
go.sum
View File

@ -24,6 +24,8 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
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 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -49,6 +51,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/gofiber/contrib/websocket v1.3.4 h1:tWeBdbJ8q0WFQXariLN4dBIbGH9KBU75s0s7YXplOSg=
github.com/gofiber/contrib/websocket v1.3.4/go.mod h1:kTFBPC6YENCnKfKx0BoOFjgXxdz7E85/STdkmZPEmPs=
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= 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 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
@ -116,6 +120,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
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/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/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=

View File

@ -23,8 +23,8 @@ func ToCurrency(f float32) Currency {
return Currency((f * 100) + 0.5) return Currency((f * 100) + 0.5)
} }
// Float64 converts a Currency to float32 // Float32 converts a Currency to float32
func (m Currency) Float64() float32 { func (m Currency) Float32() float32 {
x := float32(m) x := float32(m)
x = x / 100 x = x / 100
return x return x

View File

@ -9,6 +9,16 @@ type Company struct {
AdminID int64 AdminID int64
WalletID int64 WalletID int64
} }
type GetCompany struct {
ID int64
Name string
AdminID int64
WalletID int64
WalletBalance Currency
IsWalletActive bool
}
type CreateCompany struct { type CreateCompany struct {
Name string Name string
AdminID int64 AdminID int64

View File

@ -19,15 +19,16 @@ const (
// Transaction only represents when the user cashes out a bet in the shop // Transaction only represents when the user cashes out a bet in the shop
// It probably would be better to call it a CashOut or ShopWithdrawal // It probably would be better to call it a CashOut or ShopWithdrawal
type Transaction struct { type Transaction struct {
ID int64 ID int64
Amount Currency Amount Currency
BranchID int64 BranchID int64
CashierID int64 CashierID int64
BetID int64 BetID int64
Type TransactionType NumberOfOutcomes int64
PaymentOption PaymentOption Type TransactionType
FullName string PaymentOption PaymentOption
PhoneNumber string FullName string
PhoneNumber string
// Payment Details for bank // Payment Details for bank
BankCode string BankCode string
BeneficiaryName string BeneficiaryName string
@ -38,14 +39,15 @@ type Transaction struct {
} }
type CreateTransaction struct { type CreateTransaction struct {
Amount Currency Amount Currency
BranchID int64 BranchID int64
CashierID int64 CashierID int64
BetID int64 BetID int64
Type TransactionType NumberOfOutcomes int64
PaymentOption PaymentOption Type TransactionType
FullName string PaymentOption PaymentOption
PhoneNumber string FullName string
PhoneNumber string
// Payment Details for bank // Payment Details for bank
BankCode string BankCode string
BeneficiaryName string BeneficiaryName string

View File

@ -27,15 +27,15 @@ type User struct {
SuspendedAt time.Time SuspendedAt time.Time
Suspended bool Suspended bool
// //
BranchID int64 CompanyID ValidInt64
} }
type RegisterUserReq struct { type RegisterUserReq struct {
FirstName string FirstName string
LastName string LastName string
Email string Email string
PhoneNumber string PhoneNumber string
Password string Password string
Role string Role string
Otp string Otp string
ReferralCode string `json:"referral_code"` ReferralCode string `json:"referral_code"`
OtpMedium OtpMedium OtpMedium OtpMedium
@ -47,6 +47,8 @@ type CreateUserReq struct {
PhoneNumber string PhoneNumber string
Password string Password string
Role string Role string
Suspended bool
CompanyID ValidInt64
} }
type ResetPasswordReq struct { type ResetPasswordReq struct {
Email string Email string

View File

@ -29,13 +29,23 @@ func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (d
return domain.User{}, err return domain.User{}, err
} }
return domain.User{ return domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Password: user.Password, Password: user.Password,
Role: domain.Role(user.Role), 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,
CompanyID: domain.ValidInt64{
Value: user.CompanyID.Int64,
Valid: user.CompanyID.Valid,
},
}, nil }, nil
} }
@ -72,6 +82,25 @@ func (s *Store) GetRefreshToken(ctx context.Context, token string) (domain.Refre
Revoked: rf.Revoked, Revoked: rf.Revoked,
}, nil }, nil
} }
func (s *Store) GetRefreshTokenByUserID(ctx context.Context, id int64) (domain.RefreshToken, error) {
rf, err := s.queries.GetRefreshTokenByUserID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.RefreshToken{}, authentication.ErrRefreshTokenNotFound
}
return domain.RefreshToken{}, err
}
return domain.RefreshToken{
Token: rf.Token,
UserID: rf.UserID,
CreatedAt: rf.CreatedAt.Time,
ExpiresAt: rf.ExpiresAt.Time,
Revoked: rf.Revoked,
}, nil
}
func (s *Store) RevokeRefreshToken(ctx context.Context, token string) error { func (s *Store) RevokeRefreshToken(ctx context.Context, token string) error {
return s.queries.RevokeRefreshToken(ctx, token) return s.queries.RevokeRefreshToken(ctx, token)
} }

View File

@ -5,6 +5,7 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
) )
func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams { func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams {
@ -24,6 +25,17 @@ func convertDBCompany(dbCompany dbgen.Company) domain.Company {
} }
} }
func convertDBCompanyWithWallet(dbCompany dbgen.CompaniesWithWallet) 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,
}
}
func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company)) dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company))
if err != nil { if err != nil {
@ -32,27 +44,44 @@ func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany)
return convertDBCompany(dbCompany), nil return convertDBCompany(dbCompany), nil
} }
func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error) {
dbCompanies, err := s.queries.GetAllCompanies(ctx) dbCompanies, err := s.queries.GetAllCompanies(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var companies []domain.Company = make([]domain.Company, 0, len(dbCompanies)) var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies { for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompany(dbCompany)) companies = append(companies, convertDBCompanyWithWallet(dbCompany))
} }
return companies, nil return companies, nil
} }
func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { func (s *Store) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error) {
dbCompanies, err := s.queries.SearchCompanyByName(ctx, pgtype.Text{
String: name,
Valid: true,
})
if err != nil {
return nil, err
}
var companies []domain.GetCompany = make([]domain.GetCompany, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompanyWithWallet(dbCompany))
}
return companies, nil
}
func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) {
dbCompany, err := s.queries.GetCompanyByID(ctx, id) dbCompany, err := s.queries.GetCompanyByID(ctx, id)
if err != nil { if err != nil {
return domain.Company{}, err return domain.GetCompany{}, err
} }
return convertDBCompany(dbCompany), nil return convertDBCompanyWithWallet(dbCompany), nil
} }
func (s *Store) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { func (s *Store) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) {

View File

@ -9,19 +9,20 @@ import (
func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction {
return domain.Transaction{ return domain.Transaction{
Amount: domain.Currency(transaction.Amount), Amount: domain.Currency(transaction.Amount),
BranchID: transaction.BranchID, BranchID: transaction.BranchID,
CashierID: transaction.CashierID, CashierID: transaction.CashierID,
BetID: transaction.BetID, BetID: transaction.BetID,
Type: domain.TransactionType(transaction.Type), NumberOfOutcomes: transaction.NumberOfOutcomes,
PaymentOption: domain.PaymentOption(transaction.PaymentOption), Type: domain.TransactionType(transaction.Type),
FullName: transaction.FullName, PaymentOption: domain.PaymentOption(transaction.PaymentOption),
PhoneNumber: transaction.PhoneNumber, FullName: transaction.FullName,
BankCode: transaction.BankCode, PhoneNumber: transaction.PhoneNumber,
BeneficiaryName: transaction.BeneficiaryName, BankCode: transaction.BankCode,
AccountName: transaction.AccountName, BeneficiaryName: transaction.BeneficiaryName,
AccountNumber: transaction.AccountNumber, AccountName: transaction.AccountName,
ReferenceNumber: transaction.ReferenceNumber, AccountNumber: transaction.AccountNumber,
ReferenceNumber: transaction.ReferenceNumber,
} }
} }

View File

@ -12,7 +12,7 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) { func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error) {
err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{ err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
ID: usedOtpId, ID: usedOtpId,
UsedAt: pgtype.Timestamptz{ UsedAt: pgtype.Timestamptz{
@ -83,23 +83,44 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
Suspended: user.Suspended, Suspended: user.Suspended,
}, nil }, nil
} }
func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, error) { func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, int64, error) {
users, err := s.queries.GetAllUsers(ctx) users, err := s.queries.GetAllUsers(ctx, dbgen.GetAllUsersParams{
Role: filter.Role,
CompanyID: pgtype.Int8{
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
Limit: int32(filter.PageSize),
Offset: int32(filter.Page),
})
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
userList := make([]domain.User, len(users)) userList := make([]domain.User, len(users))
for i, user := range users { for i, user := range users {
userList[i] = domain.User{ userList[i] = domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, EmailVerified: user.EmailVerified,
Role: domain.Role(user.Role), PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended,
} }
} }
return userList, nil totalCount, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{
Role: filter.Role,
CompanyID: pgtype.Int8{
Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid,
},
})
return userList, totalCount, nil
} }
func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) { func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
@ -110,12 +131,18 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
userList := make([]domain.User, len(users)) userList := make([]domain.User, len(users))
for i, user := range users { for i, user := range users {
userList[i] = domain.User{ userList[i] = domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role), 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,
} }
} }
return userList, nil return userList, nil
@ -129,12 +156,18 @@ func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]doma
userList := make([]domain.User, len(users)) userList := make([]domain.User, len(users))
for i, user := range users { for i, user := range users {
userList[i] = domain.User{ userList[i] = domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role), 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,
} }
} }
return userList, nil return userList, nil
@ -152,12 +185,18 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string
userList := make([]domain.User, 0, len(users)) userList := make([]domain.User, 0, len(users))
for _, user := range users { for _, user := range users {
userList = append(userList, domain.User{ userList = append(userList, domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role), Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}) })
} }
return userList, nil return userList, nil
@ -216,12 +255,18 @@ func (s *Store) GetUserByEmail(ctx context.Context, email string) (domain.User,
return domain.User{}, err return domain.User{}, err
} }
return domain.User{ return domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role), Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}, nil }, nil
} }
func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) { func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) {
@ -235,13 +280,20 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.Use
} }
return domain.User{}, err return domain.User{}, err
} }
return domain.User{ return domain.User{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
Email: user.Email.String, Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role), Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}, nil }, nil
} }
@ -272,7 +324,7 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password
} }
return nil return nil
} }
func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) { func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error) {
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
FirstName: user.FirstName, FirstName: user.FirstName,
LastName: user.LastName, LastName: user.LastName,
@ -296,16 +348,26 @@ func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (dom
Time: time.Now(), Time: time.Now(),
Valid: true, Valid: true,
}, },
Suspended: user.Suspended,
CompanyID: pgtype.Int8{
Int64: user.CompanyID.Value,
Valid: user.CompanyID.Valid,
},
}) })
if err != nil { if err != nil {
return domain.User{}, err return domain.User{}, err
} }
return domain.User{ return domain.User{
ID: userRes.ID, ID: userRes.ID,
FirstName: userRes.FirstName, FirstName: userRes.FirstName,
LastName: userRes.LastName, LastName: userRes.LastName,
Email: userRes.Email.String, Email: userRes.Email.String,
PhoneNumber: userRes.PhoneNumber.String, PhoneNumber: userRes.PhoneNumber.String,
Role: domain.Role(userRes.Role), Role: domain.Role(userRes.Role),
EmailVerified: userRes.EmailVerified,
PhoneVerified: userRes.PhoneVerified,
CreatedAt: userRes.CreatedAt.Time,
UpdatedAt: userRes.UpdatedAt.Time,
Suspended: userRes.Suspended,
}, nil }, nil
} }

View File

@ -16,13 +16,14 @@ var (
ErrUserNotFound = errors.New("user not found") ErrUserNotFound = errors.New("user not found")
ErrExpiredToken = errors.New("token expired") ErrExpiredToken = errors.New("token expired")
ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again
ErrUserSuspended = errors.New("user has been suspended")
) )
type LoginSuccess struct { type LoginSuccess struct {
UserId int64 UserId int64
Role domain.Role Role domain.Role
RfToken string RfToken string
BranchId int64 CompanyID domain.ValidInt64
} }
func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) { func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) {
@ -34,6 +35,23 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
if err != nil { if err != nil {
return LoginSuccess{}, err return LoginSuccess{}, err
} }
if user.Suspended {
return LoginSuccess{}, ErrUserSuspended
}
oldRefreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
if err != nil && err != ErrRefreshTokenNotFound {
return LoginSuccess{}, err
}
// If old refresh token is not revoked, revoke it
if err == nil && !oldRefreshToken.Revoked {
err = s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token)
if(err != nil) {
return LoginSuccess{}, err
}
}
refreshToken, err := generateRefreshToken() refreshToken, err := generateRefreshToken()
if err != nil { if err != nil {
@ -45,28 +63,29 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
CreatedAt: time.Now(), CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
}) })
if err != nil { if err != nil {
return LoginSuccess{}, err return LoginSuccess{}, err
} }
return LoginSuccess{ return LoginSuccess{
UserId: user.ID, UserId: user.ID,
Role: user.Role, Role: user.Role,
RfToken: refreshToken, RfToken: refreshToken,
BranchId: user.BranchID, CompanyID: user.CompanyID,
}, nil }, nil
} }
func (s *Service) RefreshToken(ctx context.Context, refToken string) error { func (s *Service) RefreshToken(ctx context.Context, refToken string) (domain.RefreshToken, error) {
token, err := s.tokenStore.GetRefreshToken(ctx, refToken) token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
if err != nil { if err != nil {
return err return domain.RefreshToken{}, err
} }
if token.Revoked { if token.Revoked {
return ErrRefreshTokenNotFound return domain.RefreshToken{}, ErrRefreshTokenNotFound
} }
if token.ExpiresAt.Before(time.Now()) { if token.ExpiresAt.Before(time.Now()) {
return ErrExpiredToken return domain.RefreshToken{}, ErrExpiredToken
} }
// newRefToken, err := generateRefreshToken() // newRefToken, err := generateRefreshToken()
@ -80,8 +99,19 @@ func (s *Service) RefreshToken(ctx context.Context, refToken string) error {
// CreatedAt: time.Now(), // CreatedAt: time.Now(),
// ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), // ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
// }) // })
return nil return token, nil
} }
func (s *Service) GetLastLogin(ctx context.Context, user_id int64) (*time.Time, error) {
refreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user_id)
if err != nil {
return nil, err
}
return &refreshToken.CreatedAt, nil
}
func (s *Service) Logout(ctx context.Context, refToken string) error { func (s *Service) Logout(ctx context.Context, refToken string) error {
token, err := s.tokenStore.GetRefreshToken(ctx, refToken) token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
if err != nil { if err != nil {

View File

@ -12,5 +12,6 @@ type UserStore interface {
type TokenStore interface { type TokenStore interface {
CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error
GetRefreshToken(ctx context.Context, token string) (domain.RefreshToken, error) GetRefreshToken(ctx context.Context, token string) (domain.RefreshToken, error)
GetRefreshTokenByUserID(ctx context.Context, id int64) (domain.RefreshToken, error)
RevokeRefreshToken(ctx context.Context, token string) error RevokeRefreshToken(ctx context.Context, token string) error
} }

View File

@ -2,6 +2,8 @@ package bet
import ( import (
"context" "context"
"crypto/rand"
"math/big"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
) )
@ -16,6 +18,21 @@ func NewService(betStore BetStore) *Service {
} }
} }
func (s *Service) GenerateCashoutID() (string, error) {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
const length int = 13
charLen := big.NewInt(int64(len(chars)))
result := make([]byte, length)
for i := 0; i < length; i++ {
index, err := rand.Int(rand.Reader, charLen)
if err != nil {
return "", err
}
result[i] = chars[index.Int64()]
}
return string(result), nil
}
func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
return s.betStore.CreateBet(ctx, bet) return s.betStore.CreateBet(ctx, bet)

View File

@ -8,8 +8,9 @@ import (
type CompanyStore interface { type CompanyStore interface {
CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error)
GetAllCompanies(ctx context.Context) ([]domain.Company, error) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error)
GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error)
GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error)
UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error)
DeleteCompany(ctx context.Context, id int64) error DeleteCompany(ctx context.Context, id int64) error
} }

View File

@ -19,14 +19,18 @@ func NewService(companyStore CompanyStore) *Service {
func (s *Service) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { func (s *Service) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
return s.companyStore.CreateCompany(ctx, company) return s.companyStore.CreateCompany(ctx, company)
} }
func (s *Service) GetAllCompanies(ctx context.Context) ([]domain.Company, error) { func (s *Service) GetAllCompanies(ctx context.Context) ([]domain.GetCompany, error) {
return s.companyStore.GetAllCompanies(ctx) return s.companyStore.GetAllCompanies(ctx)
} }
func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) {
return s.companyStore.GetCompanyByID(ctx, id) return s.companyStore.GetCompanyByID(ctx, id)
} }
func (s *Service) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error) {
return s.companyStore.SearchCompanyByName(ctx, name)
}
func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) {
return s.companyStore.UpdateCompany(ctx, id, company) return s.companyStore.UpdateCompany(ctx, id, company)
} }

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
@ -98,63 +99,78 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
func (s *service) FetchUpcomingEvents(ctx context.Context) error { func (s *service) FetchUpcomingEvents(ctx context.Context) error {
sportIDs := []int{1} sportIDs := []int{1}
var totalPages int = 1
var page int = 0
// var limit int = 5
// var count int = 0
for _, sportID := range sportIDs { for _, sportID := range sportIDs {
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) for page != totalPages {
resp, err := http.Get(url) page = page + 1
if err != nil { url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
continue log.Printf("📡 Fetching data for event data page %d", page)
} resp, err := http.Get(url)
defer resp.Body.Close() if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
continue
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
var data struct { var data struct {
Success int `json:"success"` Success int `json:"success"`
Results []struct { Pager struct {
ID string `json:"id"` Page int `json:"page"`
SportID string `json:"sport_id"` PerPage int `json:"per_page"`
Time string `json:"time"` Total int `json:"total"`
League struct { }
ID string `json:"id"` Results []struct {
Name string `json:"name"` ID string `json:"id"`
} `json:"league"` SportID string `json:"sport_id"`
Home struct { Time string `json:"time"`
ID string `json:"id"` League struct {
Name string `json:"name"` ID string `json:"id"`
} `json:"home"` Name string `json:"name"`
Away *struct { } `json:"league"`
ID string `json:"id"` Home struct {
Name string `json:"name"` ID string `json:"id"`
} `json:"away"` Name string `json:"name"`
} `json:"results"` } `json:"home"`
} Away *struct {
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { ID string `json:"id"`
continue Name string `json:"name"`
} } `json:"away"`
} `json:"results"`
for _, ev := range data.Results { }
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64) if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
event := domain.UpcomingEvent{ continue
ID: ev.ID,
SportID: ev.SportID,
MatchName: ev.Home.Name,
HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID,
AwayTeamID: "",
HomeKitImage: "",
AwayKitImage: "",
LeagueID: ev.League.ID,
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
} }
if ev.Away != nil { for _, ev := range data.Results {
event.AwayTeam = ev.Away.Name startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
event.AwayTeamID = ev.Away.ID event := domain.UpcomingEvent{
} ID: ev.ID,
SportID: ev.SportID,
MatchName: ev.Home.Name,
HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID,
AwayTeamID: "",
HomeKitImage: "",
AwayKitImage: "",
LeagueID: ev.League.ID,
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
}
_ = s.store.SaveUpcomingEvent(ctx, event) if ev.Away != nil {
event.AwayTeam = ev.Away.Name
event.AwayTeamID = ev.Away.ID
}
_ = s.store.SaveUpcomingEvent(ctx, event)
}
totalPages = data.Pager.Total
} }
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
) )
func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq) (domain.User, error) { func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq, is_company bool) (domain.User, error) {
// Create User // Create User
// creator, err := s.userStore.GetUserByID(ctx, createrUserId) // creator, err := s.userStore.GetUserByID(ctx, createrUserId)
// if err != nil { // if err != nil {
@ -33,7 +33,9 @@ func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq) (do
Role: domain.Role(User.Role), Role: domain.Role(User.Role),
EmailVerified: true, EmailVerified: true,
PhoneVerified: true, PhoneVerified: true,
}) Suspended: User.Suspended,
CompanyID: User.CompanyID,
}, is_company)
} }
func (s *Service) DeleteUser(ctx context.Context, id int64) error { func (s *Service) DeleteUser(ctx context.Context, id int64) error {
@ -42,10 +44,10 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
} }
type Filter struct { type Filter struct {
Role string Role string
BranchId ValidBranchId CompanyID domain.ValidInt64
Page int Page int
PageSize int PageSize int
} }
type ValidRole struct { type ValidRole struct {
Value domain.Role Value domain.Role
@ -56,7 +58,7 @@ type ValidBranchId struct {
Valid bool Valid bool
} }
func (s *Service) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, error) { func (s *Service) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, int64, error) {
// Get all Users // Get all Users
return s.userStore.GetAllUsers(ctx, filter) return s.userStore.GetAllUsers(ctx, filter)
} }

View File

@ -7,10 +7,10 @@ import (
) )
type UserStore interface { type UserStore interface {
CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error)
CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
GetUserByID(ctx context.Context, id int64) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error)
GetAllUsers(ctx context.Context, filter Filter) ([]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.User, error)
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error UpdateUser(ctx context.Context, user domain.UpdateUserReq) error

View File

@ -70,7 +70,7 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms, PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
} }
// create the user and mark otp as used // create the user and mark otp as used
user, err := s.userStore.CreateUser(ctx, userR, otp.ID) user, err := s.userStore.CreateUser(ctx, userR, otp.ID, false)
if err != nil { if err != nil {
return domain.User{}, err return domain.User{}, err
} }

View File

@ -0,0 +1,156 @@
package handlers
import (
"time"
"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"
)
type CreateAdminReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
}
// CreateAdmin godoc
// @Summary Create Admin
// @Description Create Admin
// @Tags admin
// @Accept json
// @Produce json
// @Param manger body CreateAdminReq true "Create admin"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /admin [post]
func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
var companyID domain.ValidInt64
var req CreateAdminReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
// Admins can be created without company ids and can be assigned later
if req.CompanyID == nil {
companyID = domain.ValidInt64{
Value: 0,
Valid: false,
}
} else {
companyID = domain.ValidInt64{
Value: *req.CompanyID,
Valid: true,
}
}
user := domain.CreateUserReq{
FirstName: req.FirstName,
LastName: req.LastName,
Email: req.Email,
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Role: string(domain.RoleAdmin),
CompanyID: companyID,
}
_, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil {
h.logger.Error("CreateAdmin failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create admin", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Admin created successfully", nil, nil)
}
type AdminRes struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Role domain.Role `json:"role"`
EmailVerified bool `json:"email_verified"`
PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"`
}
// GetAllAdmins godoc
// @Summary Get all Admins
// @Description Get all Admins
// @Tags admin
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {object} AdminRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @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: true,
},
Page: c.QueryInt("page", 1) - 1,
PageSize: c.QueryInt("page_size", 10),
}
valErrs, ok := h.validator.Validate(c, filter)
if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil {
h.logger.Error("GetAllAdmins failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Admins", nil, nil)
}
var result []AdminRes = make([]AdminRes, len(admins))
for index, admin := range admins {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
if err != nil {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &admin.CreatedAt
} else {
h.logger.Error("Failed to get user last login", "userID", admin.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
}
result[index] = AdminRes{
ID: admin.ID,
FirstName: admin.FirstName,
LastName: admin.LastName,
Email: admin.Email,
PhoneNumber: admin.PhoneNumber,
Role: admin.Role,
EmailVerified: admin.EmailVerified,
PhoneVerified: admin.PhoneVerified,
CreatedAt: admin.CreatedAt,
UpdatedAt: admin.UpdatedAt,
SuspendedAt: admin.SuspendedAt,
Suspended: admin.Suspended,
LastLogin: *lastLogin,
}
}
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page, int(total))
}

View File

@ -3,7 +3,6 @@ package handlers
import ( import (
"errors" "errors"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -35,16 +34,6 @@ type loginCustomerRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/login [post] // @Router /auth/login [post]
func (h *Handler) LoginCustomer(c *fiber.Ctx) error { func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
type loginCustomerReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required" example:"password123"`
}
type loginCustomerRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Role string `json:"role"`
}
var req loginCustomerReq var req loginCustomerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -62,13 +51,15 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
switch { switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound): case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials") return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
case errors.Is(err, authentication.ErrUserSuspended):
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
default: default:
h.logger.Error("Login failed", "error", err) h.logger.Error("Login failed", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error") return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
} }
} }
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.BranchId, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil { if err != nil {
h.logger.Error("Failed to create access token", "userID", successRes.UserId, "error", err) h.logger.Error("Failed to create access token", "userID", successRes.UserId, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
@ -100,16 +91,14 @@ type refreshToken struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/refresh [post] // @Router /auth/refresh [post]
func (h *Handler) RefreshToken(c *fiber.Ctx) error { func (h *Handler) RefreshToken(c *fiber.Ctx) error {
type refreshTokenReq struct {
AccessToken string `json:"access_token" validate:"required" example:"<jwt-token>"`
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
type loginCustomerRes struct { type loginCustomerRes struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
Role string `json:"role"`
} }
var req refreshTokenReq var req refreshToken
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse RefreshToken request", "error", err) h.logger.Error("Failed to parse RefreshToken request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
@ -119,10 +108,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
userId := c.Locals("user_id").(int64) refreshToken, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
role := c.Locals("role").(string)
branchId := c.Locals("branch_id").(int64)
err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
if err != nil { if err != nil {
h.logger.Info("Refresh token attempt failed", "refreshToken", req.RefreshToken, "error", err) h.logger.Info("Refresh token attempt failed", "refreshToken", req.RefreshToken, "error", err)
switch { switch {
@ -136,8 +122,10 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
} }
} }
user, err := h.userSvc.GetUserByID(c.Context(), refreshToken.UserID)
// Assuming the refreshed token includes userID and role info; adjust if needed // Assuming the refreshed token includes userID and role info; adjust if needed
accessToken, err := jwtutil.CreateJwt(userId, domain.Role(role), branchId, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil { if err != nil {
h.logger.Error("Failed to create new access token", "error", err) h.logger.Error("Failed to create new access token", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
@ -146,13 +134,15 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
res := loginCustomerRes{ res := loginCustomerRes{
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: req.RefreshToken, RefreshToken: req.RefreshToken,
Role: string(user.Role),
} }
return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil)
} }
type logoutReq struct { type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"` RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
} }
// LogOutCustomer godoc // LogOutCustomer godoc
// @Summary Logout customer // @Summary Logout customer
// @Description Logout customer // @Description Logout customer
@ -166,9 +156,6 @@ type logoutReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /auth/logout [post] // @Router /auth/logout [post]
func (h *Handler) LogOutCustomer(c *fiber.Ctx) error { func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
var req logoutReq var req logoutReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {

View File

@ -3,12 +3,10 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid"
) )
type CreateBetOutcomeReq struct { type CreateBetOutcomeReq struct {
@ -24,7 +22,6 @@ type CreateBetReq struct {
Status domain.OutcomeStatus `json:"status" example:"1"` Status domain.OutcomeStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"` FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
} }
type CreateBetRes struct { type CreateBetRes struct {
@ -58,7 +55,7 @@ type BetRes struct {
func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes { func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
return CreateBetRes{ return CreateBetRes{
ID: bet.ID, ID: bet.ID,
Amount: bet.Amount.Float64(), Amount: bet.Amount.Float32(),
TotalOdds: bet.TotalOdds, TotalOdds: bet.TotalOdds,
Status: bet.Status, Status: bet.Status,
FullName: bet.FullName, FullName: bet.FullName,
@ -73,7 +70,7 @@ func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
func convertBet(bet domain.GetBet) BetRes { func convertBet(bet domain.GetBet) BetRes {
return BetRes{ return BetRes{
ID: bet.ID, ID: bet.ID,
Amount: bet.Amount.Float64(), Amount: bet.Amount.Float32(),
TotalOdds: bet.TotalOdds, TotalOdds: bet.TotalOdds,
Status: bet.Status, Status: bet.Status,
FullName: bet.FullName, FullName: bet.FullName,
@ -117,7 +114,15 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
// Validating user by role // Validating user by role
// Differentiating between offline and online bets // Differentiating between offline and online bets
user, err := h.userSvc.GetUserByID(c.Context(), userID) user, err := h.userSvc.GetUserByID(c.Context(), userID)
cashoutUUID := uuid.New() if err != nil {
h.logger.Error("CreateBetReq failed, user id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
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 var bet domain.Bet
if user.Role == domain.RoleCashier { if user.Role == domain.RoleCashier {
@ -153,8 +158,27 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
Value: userID, Value: userID,
Valid: false, Valid: false,
}, },
IsShopBet: req.IsShopBet, IsShopBet: true,
CashoutID: cashoutUUID.String(), CashoutID: cashoutID,
})
} else if user.Role == domain.RoleSuperAdmin {
// This is just for testing
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: 1,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: true,
},
IsShopBet: true,
CashoutID: cashoutID,
}) })
} else { } else {
// TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount // TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount
@ -173,8 +197,8 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
Value: userID, Value: userID,
Valid: true, Valid: true,
}, },
IsShopBet: req.IsShopBet, IsShopBet: false,
CashoutID: cashoutUUID.String(), CashoutID: cashoutID,
}) })
} }
@ -200,10 +224,10 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
} }
// Checking to make sure the event hasn't already started // Checking to make sure the event hasn't already started
currentTime := time.Now() // currentTime := time.Now()
if event.StartTime.Before(currentTime) { // if event.StartTime.Before(currentTime) {
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) // return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
} // }
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
@ -224,7 +248,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
rawBytes, err := json.Marshal(raw) rawBytes, err := json.Marshal(raw)
err = json.Unmarshal(rawBytes, &rawOdd) err = json.Unmarshal(rawBytes, &rawOdd)
if err != nil { if err != nil {
h.logger.Error("Failed to unmarshal raw odd:", err) h.logger.Error("Failed to unmarshal raw odd", "error", err)
continue continue
} }
if rawOdd.ID == oddIDStr { if rawOdd.ID == oddIDStr {

View File

@ -1,10 +1,12 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -16,6 +18,7 @@ type CreateCashierReq struct {
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
BranchID int64 `json:"branch_id" example:"1"` BranchID int64 `json:"branch_id" example:"1"`
Suspended bool `json:"suspended" example:"false"`
} }
// CreateCashier godoc // CreateCashier godoc
@ -31,6 +34,10 @@ type CreateCashierReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashiers [post] // @Router /cashiers [post]
func (h *Handler) CreateCashier(c *fiber.Ctx) error { func (h *Handler) CreateCashier(c *fiber.Ctx) error {
// Get user_id from middleware
companyID := c.Locals("company_id").(domain.ValidInt64)
var req CreateCashierReq var req CreateCashierReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err) h.logger.Error("RegisterUser failed", "error", err)
@ -47,8 +54,11 @@ func (h *Handler) CreateCashier(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
Password: req.Password, Password: req.Password,
Role: string(domain.RoleCashier), Role: string(domain.RoleCashier),
Suspended: req.Suspended,
CompanyID: companyID,
} }
newUser, err := h.userSvc.CreateUser(c.Context(), userRequest) fmt.Print(req.Suspended)
newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true)
if err != nil { if err != nil {
h.logger.Error("CreateCashier failed", "error", err) h.logger.Error("CreateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create cashier", nil, nil)
@ -76,6 +86,7 @@ type GetCashierRes struct {
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
LastLogin time.Time `json:"last_login"`
} }
// GetAllCashiers godoc // GetAllCashiers godoc
@ -113,9 +124,19 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
} }
var result []GetCashierRes var result []GetCashierRes = make([]GetCashierRes, 0, len(cashiers))
for _, cashier := range 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{ result = append(result, GetCashierRes{
ID: cashier.ID, ID: cashier.ID,
FirstName: cashier.FirstName, FirstName: cashier.FirstName,
@ -129,6 +150,7 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
UpdatedAt: cashier.UpdatedAt, UpdatedAt: cashier.UpdatedAt,
SuspendedAt: cashier.SuspendedAt, SuspendedAt: cashier.SuspendedAt,
Suspended: cashier.Suspended, Suspended: cashier.Suspended,
LastLogin: *lastLogin,
}) })
} }
@ -148,6 +170,7 @@ type updateUserReq struct {
// @Tags cashier // @Tags cashier
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Cashier ID"
// @Param cashier body updateUserReq true "Update cashier" // @Param cashier body updateUserReq true "Update cashier"
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
@ -155,6 +178,12 @@ type updateUserReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /cashiers/{id} [put] // @Router /cashiers/{id} [put]
func (h *Handler) UpdateCashier(c *fiber.Ctx) error { func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
cashierIdStr := c.Params("id")
cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64)
if err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
}
var req updateUserReq var req updateUserReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("UpdateCashier failed", "error", err) h.logger.Error("UpdateCashier failed", "error", err)
@ -166,12 +195,7 @@ func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
cashierIdStr := c.Params("id")
cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64)
if err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
}
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: cashierId, UserId: cashierId,
FirstName: domain.ValidString{ FirstName: domain.ValidString{

View File

@ -20,6 +20,15 @@ type CompanyRes struct {
WalletID int64 `json:"wallet_id" example:"1"` WalletID int64 `json:"wallet_id" example:"1"`
} }
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"`
}
func convertCompany(company domain.Company) CompanyRes { func convertCompany(company domain.Company) CompanyRes {
return CompanyRes{ return CompanyRes{
ID: company.ID, ID: company.ID,
@ -29,6 +38,17 @@ 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,
}
}
// CreateCompany godoc // CreateCompany godoc
// @Summary Create a company // @Summary Create a company
// @Description Creates a company // @Description Creates a company
@ -100,10 +120,10 @@ func (h *Handler) GetAllCompanies(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil)
} }
var result []CompanyRes = make([]CompanyRes, 0, len(companies)) var result []GetCompanyRes = make([]GetCompanyRes, 0, len(companies))
for _, company := range companies { for _, company := range companies {
result = append(result, convertCompany(company)) result = append(result, convertGetCompany(company))
} }
return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil) return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil)
@ -137,12 +157,43 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to company branch", err, nil)
} }
res := convertCompany(company) res := convertGetCompany(company)
return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Company retrieved successfully", res, nil)
} }
// GetAllCompanies godoc
// @Summary Gets all companies
// @Description Gets all companies
// @Tags company
// @Accept json
// @Produce json
// @Success 200 {array} CompanyRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /search/company [get]
func (h *Handler) SearchCompany(c *fiber.Ctx) error {
searchQuery := c.Query("q")
if searchQuery == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Search query is required", nil, nil)
}
companies, err := h.companySvc.SearchCompanyByName(c.Context(), searchQuery)
if err != nil {
h.logger.Error("Failed to get companies", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get companies", err, nil)
}
var result []GetCompanyRes = make([]GetCompanyRes, 0, len(companies))
for _, company := range companies {
result = append(result, convertGetCompany(company))
}
return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil)
}
// UpdateCompany godoc // UpdateCompany godoc
// @Summary Updates a company // @Summary Updates a company
// @Description Updates a company // @Description Updates a company

View File

@ -2,8 +2,10 @@ package handlers
import ( import (
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -15,11 +17,12 @@ type CreateManagerReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
} }
// CreateManagers godoc // CreateManager godoc
// @Summary Create Managers // @Summary Create Manager
// @Description Create Managers // @Description Create Manager
// @Tags manager // @Tags manager
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -30,6 +33,9 @@ type CreateManagerReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /managers [post] // @Router /managers [post]
func (h *Handler) CreateManager(c *fiber.Ctx) error { func (h *Handler) CreateManager(c *fiber.Ctx) error {
// Get user_id from middleware
var req CreateManagerReq var req CreateManagerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err) h.logger.Error("RegisterUser failed", "error", err)
@ -39,6 +45,22 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
var companyID domain.ValidInt64
role := c.Locals("role").(domain.Role)
if role == domain.RoleSuperAdmin {
if req.CompanyID == nil {
h.logger.Error("RegisterUser failed error: company id is required")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", "Company ID is required", nil)
}
companyID = domain.ValidInt64{
Value: *req.CompanyID,
Valid: true,
}
} else {
companyID = c.Locals("company_id").(domain.ValidInt64)
}
user := domain.CreateUserReq{ user := domain.CreateUserReq{
FirstName: req.FirstName, FirstName: req.FirstName,
LastName: req.LastName, LastName: req.LastName,
@ -46,16 +68,33 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
Password: req.Password, Password: req.Password,
Role: string(domain.RoleBranchManager), Role: string(domain.RoleBranchManager),
CompanyID: companyID,
} }
_, err := h.userSvc.CreateUser(c.Context(), user) _, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil { if err != nil {
h.logger.Error("CreateManagers failed", "error", err) h.logger.Error("CreateManager failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create Managers", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create manager", nil, nil)
} }
return response.WriteJSON(c, fiber.StatusOK, "Managers created successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil)
} }
type ManagersRes struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Role domain.Role `json:"role"`
EmailVerified bool `json:"email_verified"`
PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"`
}
// GetAllManagers godoc // GetAllManagers godoc
// @Summary Get all Managers // @Summary Get all Managers
// @Description Get all Managers // @Description Get all Managers
@ -64,7 +103,7 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
// @Produce json // @Produce json
// @Param page query int false "Page number" // @Param page query int false "Page number"
// @Param page_size query int false "Page size" // @Param page_size query int false "Page size"
// @Success 200 {object} response.APIResponse // @Success 200 {object} ManagersRes
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
@ -72,24 +111,52 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
func (h *Handler) GetAllManagers(c *fiber.Ctx) error { func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
filter := user.Filter{ filter := user.Filter{
Role: string(domain.RoleBranchManager), Role: string(domain.RoleBranchManager),
BranchId: user.ValidBranchId{ CompanyID: domain.ValidInt64{
Value: int64(c.QueryInt("branch_id")), Value: int64(c.QueryInt("company_id")),
Valid: true, Valid: true,
}, },
Page: c.QueryInt("page", 1), Page: c.QueryInt("page", 1) - 1,
PageSize: c.QueryInt("page_size", 10), PageSize: c.QueryInt("page_size", 10),
} }
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
Managers, err := h.userSvc.GetAllUsers(c.Context(), filter) managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil { if err != nil {
h.logger.Error("GetAllManagers failed", "error", err) h.logger.Error("GetAllManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil)
} }
return response.WriteJSON(c, fiber.StatusOK, "Managers retrieved successfully", Managers, nil) var result []ManagersRes = make([]ManagersRes, len(managers))
for index, manager := range managers {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), manager.ID)
if err != nil {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &manager.CreatedAt
} else {
h.logger.Error("Failed to get user last login", "userID", manager.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
}
result[index] = ManagersRes{
ID: manager.ID,
FirstName: manager.FirstName,
LastName: manager.LastName,
Email: manager.Email,
PhoneNumber: manager.PhoneNumber,
Role: manager.Role,
EmailVerified: manager.EmailVerified,
PhoneVerified: manager.PhoneVerified,
CreatedAt: manager.CreatedAt,
UpdatedAt: manager.UpdatedAt,
SuspendedAt: manager.SuspendedAt,
Suspended: manager.Suspended,
LastLogin: *lastLogin,
}
}
return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page, int(total))
} }

View File

@ -3,7 +3,6 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -77,10 +76,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
} }
// Checking to make sure the event hasn't already started // Checking to make sure the event hasn't already started
currentTime := time.Now() // currentTime := time.Now()
if event.StartTime.Before(currentTime) { // if event.StartTime.Before(currentTime) {
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) // return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
} // }
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
@ -184,14 +183,14 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id) ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
if err != nil { 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.StatusInternalServerError, "Failed to retrieve ticket") return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
} }
res := TicketRes{ res := TicketRes{
ID: ticket.ID, ID: ticket.ID,
Outcomes: ticket.Outcomes, Outcomes: ticket.Outcomes,
Amount: ticket.Amount.Float64(), Amount: ticket.Amount.Float32(),
TotalOdds: ticket.TotalOdds, TotalOdds: ticket.TotalOdds,
} }
return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil)
@ -220,7 +219,7 @@ func (h *Handler) GetAllTickets(c *fiber.Ctx) error {
res[i] = TicketRes{ res[i] = TicketRes{
ID: ticket.ID, ID: ticket.ID,
Outcomes: ticket.Outcomes, Outcomes: ticket.Outcomes,
Amount: ticket.Amount.Float64(), Amount: ticket.Amount.Float32(),
TotalOdds: ticket.TotalOdds, TotalOdds: ticket.TotalOdds,
} }
} }

View File

@ -9,21 +9,22 @@ import (
) )
type TransactionRes struct { type TransactionRes struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
Amount float32 `json:"amount" example:"100.0"` Amount float32 `json:"amount" example:"100.0"`
BranchID int64 `json:"branch_id" example:"1"` BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"` CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"` BetID int64 `json:"bet_id" example:"1"`
Type int64 `json:"type" example:"1"` NumberOfOutcomes int64 `json:"number_of_outcomes" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` Type int64 `json:"type" example:"1"`
FullName string `json:"full_name" example:"John Smith"` PaymentOption domain.PaymentOption `json:"payment_option" example:"1"`
PhoneNumber string `json:"phone_number" example:"0911111111"` FullName string `json:"full_name" example:"John Smith"`
BankCode string `json:"bank_code"` PhoneNumber string `json:"phone_number" example:"0911111111"`
BeneficiaryName string `json:"beneficiary_name"` BankCode string `json:"bank_code"`
AccountName string `json:"account_name"` BeneficiaryName string `json:"beneficiary_name"`
AccountNumber string `json:"account_number"` AccountName string `json:"account_name"`
ReferenceNumber string `json:"reference_number"` AccountNumber string `json:"account_number"`
Verified bool `json:"verified" example:"true"` ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified" example:"true"`
} }
type CreateTransactionReq struct { type CreateTransactionReq struct {
@ -44,7 +45,7 @@ type CreateTransactionReq struct {
func convertTransaction(transaction domain.Transaction) TransactionRes { func convertTransaction(transaction domain.Transaction) TransactionRes {
return TransactionRes{ return TransactionRes{
ID: transaction.ID, ID: transaction.ID,
Amount: transaction.Amount.Float64(), Amount: transaction.Amount.Float32(),
BranchID: transaction.BranchID, BranchID: transaction.BranchID,
CashierID: transaction.CashierID, CashierID: transaction.CashierID,
BetID: transaction.BetID, BetID: transaction.BetID,
@ -115,20 +116,22 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
// TODO: Validate the bet id and add the number of outcomes
transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
BranchID: branchID, BranchID: branchID,
CashierID: userID, CashierID: userID,
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
BetID: req.BetID, BetID: req.BetID,
Type: domain.TransactionType(req.Type), NumberOfOutcomes: 1,
PaymentOption: domain.PaymentOption(req.PaymentOption), Type: domain.TransactionType(req.Type),
FullName: req.FullName, PaymentOption: domain.PaymentOption(req.PaymentOption),
PhoneNumber: req.PhoneNumber, FullName: req.FullName,
BankCode: req.BankCode, PhoneNumber: req.PhoneNumber,
BeneficiaryName: req.BeneficiaryName, BankCode: req.BankCode,
AccountName: req.AccountName, BeneficiaryName: req.BeneficiaryName,
AccountNumber: req.AccountNumber, AccountName: req.AccountName,
ReferenceNumber: req.ReferenceNumber, AccountNumber: req.AccountNumber,
ReferenceNumber: req.ReferenceNumber,
}) })
if err != nil { if err != nil {
@ -233,8 +236,9 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
} }
type UpdateTransactionVerifiedReq struct { type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"` Verified bool `json:"verified" validate:"required" example:"true"`
} }
// UpdateTransactionVerified godoc // UpdateTransactionVerified godoc
// @Summary Updates the verified field of a transaction // @Summary Updates the verified field of a transaction
// @Description Updates the verified status of a transaction // @Description Updates the verified status of a transaction
@ -248,9 +252,6 @@ type UpdateTransactionVerifiedReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /transaction/{id} [patch] // @Router /transaction/{id} [patch]
func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"`
}
transactionID := c.Params("id") transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64) id, err := strconv.ParseInt(transactionID, 10, 64)

View File

@ -47,7 +47,7 @@ func convertTransfer(transfer domain.Transfer) TransferWalletRes {
return TransferWalletRes{ return TransferWalletRes{
ID: transfer.ID, ID: transfer.ID,
Amount: transfer.Amount.Float64(), Amount: transfer.Amount.Float32(),
Verified: transfer.Verified, Verified: transfer.Verified,
Type: string(transfer.Type), Type: string(transfer.Type),
PaymentMethod: string(transfer.PaymentMethod), PaymentMethod: string(transfer.PaymentMethod),

View File

@ -2,13 +2,16 @@ package handlers
import ( import (
"errors" "errors"
"strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type CheckPhoneEmailExistReq struct { type CheckPhoneEmailExistReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
@ -17,6 +20,15 @@ type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"` EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"` PhoneNumberExist bool `json:"phone_number_exist"`
} }
type CheckPhoneEmailExistReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
// CheckPhoneEmailExist godoc // CheckPhoneEmailExist godoc
// @Summary Check if phone number or email exist // @Summary Check if phone number or email exist
// @Description Check if phone number or email exist // @Description Check if phone number or email exist
@ -29,14 +41,6 @@ type CheckPhoneEmailExistRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/checkPhoneEmailExist [post] // @Router /user/checkPhoneEmailExist [post]
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
type CheckPhoneEmailExistReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
var req CheckPhoneEmailExistReq var req CheckPhoneEmailExistReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -60,10 +64,16 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
} }
return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil)
} }
type RegisterCodeReq struct { type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
} }
type RegisterCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
// SendRegisterCode godoc // SendRegisterCode godoc
// @Summary Send register code // @Summary Send register code
// @Description Send register code // @Description Send register code
@ -76,10 +86,6 @@ type RegisterCodeReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post] // @Router /user/sendRegisterCode [post]
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
type RegisterCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req RegisterCodeReq var req RegisterCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -110,18 +116,17 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
} }
type RegisterUserReq struct { type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"` LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
//Role string
Otp string `json:"otp" example:"123456"` Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"` ReferalCode string `json:"referal_code" example:"ABC123"`
//
} }
// RegisterUser godoc // RegisterUser godoc
// @Summary Register user // @Summary Register user
// @Description Register user // @Description Register user
@ -134,15 +139,6 @@ type RegisterUserReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/register [post] // @Router /user/register [post]
func (h *Handler) RegisterUser(c *fiber.Ctx) error { func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
}
var req RegisterUserReq var req RegisterUserReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -215,8 +211,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type ResetCodeReq struct { type ResetCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
} }
// SendResetCode godoc // SendResetCode godoc
// @Summary Send reset code // @Summary Send reset code
// @Description Send reset code // @Description Send reset code
@ -229,10 +226,6 @@ type ResetCodeReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post] // @Router /user/sendResetCode [post]
func (h *Handler) SendResetCode(c *fiber.Ctx) error { func (h *Handler) SendResetCode(c *fiber.Ctx) error {
type ResetCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req ResetCodeReq var req ResetCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -263,12 +256,20 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
} }
type ResetPasswordReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
Otp string `json:"otp" validate:"required" example:"123456"`
}
type ResetPasswordReq struct { type ResetPasswordReq struct {
Email string Email string
PhoneNumber string PhoneNumber string
Password string Password string
Otp string Otp string
} }
// ResetPassword godoc // ResetPassword godoc
// @Summary Reset password // @Summary Reset password
// @Description Reset password // @Description Reset password
@ -281,12 +282,6 @@ type ResetPasswordReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post] // @Router /user/resetPassword [post]
func (h *Handler) ResetPassword(c *fiber.Ctx) error { func (h *Handler) ResetPassword(c *fiber.Ctx) error {
type ResetPasswordReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
Otp string `json:"otp" validate:"required" example:"123456"`
}
var req ResetPasswordReq var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -331,6 +326,7 @@ type UserProfileRes struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
} }
@ -360,6 +356,15 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile")
} }
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", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
lastLogin = &user.CreatedAt
}
res := UserProfileRes{ res := UserProfileRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
@ -373,6 +378,7 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt, SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin,
} }
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
} }
@ -404,6 +410,7 @@ type SearchUserByNameOrPhoneReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/search [post] // @Router /user/search [post]
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// TODO: Add filtering by role based on which user is calling this
var req SearchUserByNameOrPhoneReq var req SearchUserByNameOrPhoneReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("SearchUserByNameOrPhone failed", "error", err) h.logger.Error("SearchUserByNameOrPhone failed", "error", err)
@ -425,6 +432,15 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
} }
var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) var res []UserProfileRes = make([]UserProfileRes, 0, len(users))
for _, user := range users { for _, user := range users {
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 = append(res, UserProfileRes{ res = append(res, UserProfileRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
@ -438,8 +454,80 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt, SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin,
}) })
} }
return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil)
} }
// GetUserByID godoc
// @Summary Get user by id
// @Description Get a single user by 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 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/single/{id} [get]
func (h *Handler) GetUserByID(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("UpdateCashier failed", "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)
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", "userID", user.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
lastLogin = &user.CreatedAt
}
res := UserProfileRes{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
PhoneNumber: user.PhoneNumber,
Role: user.Role,
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
}
return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", res, nil)
}

View File

@ -15,6 +15,16 @@ type launchVirtualGameRes struct {
LaunchURL string `json:"launch_url"` LaunchURL string `json:"launch_url"`
} }
type launchVirtualGameReq struct {
GameID string `json:"game_id" validate:"required" example:"crash_001"`
Currency string `json:"currency" validate:"required,len=3" example:"USD"`
Mode string `json:"mode" validate:"required,oneof=REAL DEMO" example:"REAL"`
}
type launchVirtualGameRes struct {
LaunchURL string `json:"launch_url"`
}
// LaunchVirtualGame godoc // LaunchVirtualGame godoc
// @Summary Launch a PopOK virtual game // @Summary Launch a PopOK virtual game
// @Description Generates a URL to launch a PopOK game // @Description Generates a URL to launch a PopOK game
@ -29,15 +39,6 @@ type launchVirtualGameRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /virtual-game/launch [post] // @Router /virtual-game/launch [post]
func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error { func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
type launchVirtualGameReq struct {
GameID string `json:"game_id" validate:"required" example:"crash_001"`
Currency string `json:"currency" validate:"required,len=3" example:"USD"`
Mode string `json:"mode" validate:"required,oneof=REAL DEMO" example:"REAL"`
}
type launchVirtualGameRes struct {
LaunchURL string `json:"launch_url"`
}
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {

View File

@ -27,7 +27,7 @@ type WalletRes struct {
func convertWallet(wallet domain.Wallet) WalletRes { func convertWallet(wallet domain.Wallet) WalletRes {
return WalletRes{ return WalletRes{
ID: wallet.ID, ID: wallet.ID,
Balance: wallet.Balance.Float64(), Balance: wallet.Balance.Float32(),
IsWithdraw: wallet.IsWithdraw, IsWithdraw: wallet.IsWithdraw,
IsBettable: wallet.IsBettable, IsBettable: wallet.IsBettable,
IsTransferable: wallet.IsTransferable, IsTransferable: wallet.IsTransferable,
@ -55,9 +55,9 @@ func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
return CustomerWalletRes{ return CustomerWalletRes{
ID: wallet.ID, ID: wallet.ID,
RegularID: wallet.RegularID, RegularID: wallet.RegularID,
RegularBalance: wallet.RegularBalance.Float64(), RegularBalance: wallet.RegularBalance.Float32(),
StaticID: wallet.StaticID, StaticID: wallet.StaticID,
StaticBalance: wallet.StaticBalance.Float64(), StaticBalance: wallet.StaticBalance.Float32(),
CustomerID: wallet.CustomerID, CustomerID: wallet.CustomerID,
CompanyID: wallet.CompanyID, CompanyID: wallet.CompanyID,
RegularUpdatedAt: wallet.RegularUpdatedAt, RegularUpdatedAt: wallet.RegularUpdatedAt,
@ -162,7 +162,7 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
for _, wallet := range wallets { for _, wallet := range wallets {
res = append(res, BranchWalletRes{ res = append(res, BranchWalletRes{
ID: wallet.ID, ID: wallet.ID,
Balance: wallet.Balance.Float64(), Balance: wallet.Balance.Float32(),
IsActive: wallet.IsActive, IsActive: wallet.IsActive,
Name: wallet.Name, Name: wallet.Name,
Location: wallet.Location, Location: wallet.Location,
@ -178,6 +178,10 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
} }
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
// UpdateWalletActive godoc // UpdateWalletActive godoc
// @Summary Activate and Deactivate Wallet // @Summary Activate and Deactivate Wallet
// @Description Can activate and deactivate wallet // @Description Can activate and deactivate wallet
@ -191,9 +195,6 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [patch] // @Router /wallet/{id} [patch]
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error { func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
walletID := c.Params("id") walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64) id, err := strconv.ParseInt(walletID, 10, 64)

View File

@ -17,9 +17,9 @@ var (
type UserClaim struct { type UserClaim struct {
jwt.RegisteredClaims jwt.RegisteredClaims
UserId int64 UserId int64
Role domain.Role Role domain.Role
BranchId int64 CompanyID domain.ValidInt64
} }
type PopOKClaim struct { type PopOKClaim struct {
@ -37,7 +37,7 @@ type JwtConfig struct {
JwtAccessExpiry int JwtAccessExpiry int
} }
func CreateJwt(userId int64, Role domain.Role, BranchId int64, key string, expiry int) (string, error) { func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
Issuer: "github.com/lafetz/snippitstash", Issuer: "github.com/lafetz/snippitstash",
@ -46,9 +46,9 @@ func CreateJwt(userId int64, Role domain.Role, BranchId int64, key string, expir
NotBefore: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)),
}, },
UserId: userId, UserId: userId,
Role: Role, Role: Role,
BranchId: BranchId, CompanyID: CompanyID,
}) })
jwtToken, err := token.SignedString([]byte(key)) jwtToken, err := token.SignedString([]byte(key))
return jwtToken, err return jwtToken, err

View File

@ -44,7 +44,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
} }
c.Locals("user_id", claim.UserId) c.Locals("user_id", claim.UserId)
c.Locals("role", claim.Role) c.Locals("role", claim.Role)
c.Locals("branch_id", claim.BranchId) c.Locals("company_id", claim.CompanyID)
c.Locals("refresh_token", refreshToken) c.Locals("refresh_token", refreshToken)
return c.Next() return c.Next()

View File

@ -7,6 +7,7 @@ import (
_ "github.com/SamuelTariku/FortuneBet-Backend/docs" _ "github.com/SamuelTariku/FortuneBet-Backend/docs"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/handlers" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/handlers"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
fiberSwagger "github.com/swaggo/fiber-swagger" fiberSwagger "github.com/swaggo/fiber-swagger"
) )
@ -33,7 +34,7 @@ func (a *App) initAppRoutes() {
// Auth Routes // Auth Routes
a.fiber.Post("/auth/login", h.LoginCustomer) a.fiber.Post("/auth/login", h.LoginCustomer)
a.fiber.Post("/auth/refresh", a.authMiddleware, h.RefreshToken) a.fiber.Post("/auth/refresh", h.RefreshToken)
a.fiber.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer) a.fiber.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error { a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
@ -68,6 +69,7 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/user/sendRegisterCode", h.SendRegisterCode) a.fiber.Post("/user/sendRegisterCode", h.SendRegisterCode)
a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist) a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile) a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile)
a.fiber.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet) a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone) a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
@ -81,15 +83,15 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/cashiers", a.authMiddleware, h.GetAllCashiers) a.fiber.Get("/cashiers", a.authMiddleware, h.GetAllCashiers)
a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier) a.fiber.Post("/cashiers", a.authMiddleware, h.CreateCashier)
a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier) a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier)
//
a.fiber.Get("/admin", a.authMiddleware, h.GetAllAdmins)
a.fiber.Post("/admin", a.authMiddleware, h.CreateAdmin)
a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers) a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers)
a.fiber.Post("/managers", a.authMiddleware, h.CreateManager) a.fiber.Post("/managers", a.authMiddleware, h.CreateManager)
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers) a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID) a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID)
a.fiber.Get("/prematch/odds/:event_id", h.GetPrematchOdds) a.fiber.Get("/prematch/odds/:event_id", h.GetPrematchOdds)
a.fiber.Get("/prematch/odds", h.GetALLPrematchOdds) a.fiber.Get("/prematch/odds", h.GetALLPrematchOdds)
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID) a.fiber.Get("/prematch/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
@ -125,6 +127,8 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCompanyByID) a.fiber.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCompanyByID)
a.fiber.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany) a.fiber.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany)
a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany) a.fiber.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany)
a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID)
a.fiber.Get("/search/company", a.authMiddleware, h.SearchCompany)
// Ticket Routes // Ticket Routes
a.fiber.Post("/ticket", h.CreateTicket) a.fiber.Post("/ticket", h.CreateTicket)
@ -132,12 +136,12 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/ticket/:id", h.GetTicketByID) a.fiber.Get("/ticket/:id", h.GetTicketByID)
// Bet Routes // Bet Routes
a.fiber.Post("/bet", h.CreateBet) a.fiber.Post("/bet", a.authMiddleware, h.CreateBet)
a.fiber.Get("/bet", h.GetAllBet) a.fiber.Get("/bet", a.authMiddleware, h.GetAllBet)
a.fiber.Get("/bet/:id", h.GetBetByID) a.fiber.Get("/bet/:id", a.authMiddleware, h.GetBetByID)
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID) a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)
a.fiber.Patch("/bet/:id", h.UpdateCashOut) a.fiber.Patch("/bet/:id", a.authMiddleware, h.UpdateCashOut)
a.fiber.Delete("/bet/:id", h.DeleteBet) a.fiber.Delete("/bet/:id", a.authMiddleware, h.DeleteBet)
// Wallet // Wallet
a.fiber.Get("/wallet", h.GetAllWallets) a.fiber.Get("/wallet", h.GetAllWallets)
@ -152,10 +156,10 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet) a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
// Transactions /transactions // Transactions /transactions
a.fiber.Post("/transaction", h.CreateTransaction) a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction)
a.fiber.Get("/transaction", h.GetAllTransactions) a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions)
a.fiber.Get("/transaction/:id", h.GetTransactionByID) a.fiber.Get("/transaction/:id", a.authMiddleware, h.GetTransactionByID)
a.fiber.Patch("/transaction/:id", h.UpdateTransactionVerified) a.fiber.Patch("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
// Notification Routes // Notification Routes
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket) a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)