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,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
--
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended
company_id BIGINT,
suspended_at TIMESTAMPTZ NULL,
-- this can be NULL if the user is not suspended
suspended BOOLEAN NOT NULL DEFAULT FALSE,
CHECK (
email IS NOT NULL
@ -53,6 +54,7 @@ CREATE TABLE IF NOT EXISTS bets (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_shop_bet BOOLEAN NOT NULL,
UNIQUE(cashout_id),
CHECK (
user_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,
cashier_id BIGINT NOT NULL,
bet_id BIGINT NOT NULL,
number_of_outcomes BIGINT NOT NULL,
type BIGINT NOT NULL,
payment_option BIGINT NOT NULL,
full_name VARCHAR(255) NOT NULL,
@ -247,6 +250,12 @@ CREATE TABLE companies (
admin_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
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE bets
@ -275,6 +284,9 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
ALTER TABLE branch_cashiers
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id),
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
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-------------------------------------------------------------
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

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

View File

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

View File

@ -9,9 +9,24 @@ INSERT INTO users (
email_verified,
phone_verified,
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,
first_name,
last_name,
@ -21,7 +36,9 @@ RETURNING id,
email_verified,
phone_verified,
created_at,
updated_at;
updated_at,
suspended,
company_id;
-- name: GetUserByID :one
SELECT *
FROM users
@ -36,8 +53,31 @@ SELECT id,
email_verified,
phone_verified,
created_at,
updated_at
FROM users;
updated_at,
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
SELECT id,
first_name,
@ -48,7 +88,10 @@ SELECT id,
email_verified,
phone_verified,
created_at,
updated_at
updated_at,
suspended,
suspended_at,
company_id
FROM users
WHERE first_name ILIKE '%' || $1 || '%'
OR last_name ILIKE '%' || $1 || '%'
@ -62,6 +105,12 @@ SET first_name = $1,
role = $5,
updated_at = $6
WHERE id = $7;
-- name: SuspendUser :exec
UPDATE users
SET suspended = $1,
suspended_at = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $3;
-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1;
@ -88,7 +137,10 @@ SELECT id,
email_verified,
phone_verified,
created_at,
updated_at
updated_at,
suspended,
suspended_at,
company_id
FROM users
WHERE email = $1;
-- name: GetUserByPhone :one
@ -101,7 +153,10 @@ SELECT id,
email_verified,
phone_verified,
created_at,
updated_at
updated_at,
suspended,
suspended_at,
company_id
FROM users
WHERE phone_number = $1;
-- name: UpdatePassword :exec

View File

@ -24,6 +24,111 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"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": {
"post": {
"description": "Login customer",
@ -955,6 +1060,13 @@ const docTemplate = `{
],
"summary": "Update cashier",
"parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Update cashier",
"name": "cashier",
@ -1361,7 +1473,7 @@ const docTemplate = `{
}
},
"post": {
"description": "Create Managers",
"description": "Create Manager",
"consumes": [
"application/json"
],
@ -1371,7 +1483,7 @@ const docTemplate = `{
"tags": [
"manager"
],
"summary": "Create Managers",
"summary": "Create Manager",
"parameters": [
{
"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": {
"get": {
"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": {
"get": {
"security": [
@ -3651,6 +3851,9 @@ const docTemplate = `{
},
"handlers.CheckPhoneEmailExistReq": {
"type": "object",
"required": [
"phone_number"
],
"properties": {
"email": {
"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": {
"type": "object",
"properties": {
@ -3722,10 +3954,6 @@ const docTemplate = `{
"type": "string",
"example": "John"
},
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": {
"type": "array",
"items": {
@ -3820,6 +4048,10 @@ const docTemplate = `{
"phone_number": {
"type": "string",
"example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
}
}
},
@ -3839,6 +4071,10 @@ const docTemplate = `{
"handlers.CreateManagerReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
@ -4060,7 +4296,6 @@ const docTemplate = `{
"example": "Doe"
},
"otp": {
"description": "Role string",
"type": "string",
"example": "123456"
},
@ -4093,18 +4328,27 @@ const docTemplate = `{
},
"handlers.ResetPasswordReq": {
"type": "object",
"required": [
"otp",
"password"
],
"properties": {
"email": {
"type": "string"
"type": "string",
"example": "john.doe@example.com"
},
"otp": {
"type": "string"
"type": "string",
"example": "123456"
},
"password": {
"type": "string"
"type": "string",
"minLength": 8,
"example": "newpassword123"
},
"phoneNumber": {
"type": "string"
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
@ -4195,6 +4439,10 @@ const docTemplate = `{
"type": "integer",
"example": 1
},
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": {
"allOf": [
{
@ -4315,6 +4563,9 @@ const docTemplate = `{
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},

View File

@ -16,6 +16,111 @@
"version": "1.0"
},
"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": {
"post": {
"description": "Login customer",
@ -947,6 +1052,13 @@
],
"summary": "Update cashier",
"parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Update cashier",
"name": "cashier",
@ -1353,7 +1465,7 @@
}
},
"post": {
"description": "Create Managers",
"description": "Create Manager",
"consumes": [
"application/json"
],
@ -1363,7 +1475,7 @@
"tags": [
"manager"
],
"summary": "Create Managers",
"summary": "Create Manager",
"parameters": [
{
"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": {
"get": {
"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": {
"get": {
"security": [
@ -3643,6 +3843,9 @@
},
"handlers.CheckPhoneEmailExistReq": {
"type": "object",
"required": [
"phone_number"
],
"properties": {
"email": {
"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": {
"type": "object",
"properties": {
@ -3714,10 +3946,6 @@
"type": "string",
"example": "John"
},
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": {
"type": "array",
"items": {
@ -3812,6 +4040,10 @@
"phone_number": {
"type": "string",
"example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
}
}
},
@ -3831,6 +4063,10 @@
"handlers.CreateManagerReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
@ -4052,7 +4288,6 @@
"example": "Doe"
},
"otp": {
"description": "Role string",
"type": "string",
"example": "123456"
},
@ -4085,18 +4320,27 @@
},
"handlers.ResetPasswordReq": {
"type": "object",
"required": [
"otp",
"password"
],
"properties": {
"email": {
"type": "string"
"type": "string",
"example": "john.doe@example.com"
},
"otp": {
"type": "string"
"type": "string",
"example": "123456"
},
"password": {
"type": "string"
"type": "string",
"minLength": 8,
"example": "newpassword123"
},
"phoneNumber": {
"type": "string"
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
@ -4187,6 +4431,10 @@
"type": "integer",
"example": 1
},
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": {
"allOf": [
{
@ -4307,6 +4555,9 @@
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},

View File

@ -384,6 +384,8 @@ definitions:
phone_number:
example: "1234567890"
type: string
required:
- phone_number
type: object
handlers.CheckPhoneEmailExistRes:
properties:
@ -407,6 +409,27 @@ definitions:
example: 1
type: integer
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:
properties:
event_id:
@ -427,9 +450,6 @@ definitions:
full_name:
example: John
type: string
is_shop_bet:
example: false
type: boolean
outcomes:
items:
$ref: '#/definitions/handlers.CreateBetOutcomeReq'
@ -496,6 +516,9 @@ definitions:
phone_number:
example: "1234567890"
type: string
suspended:
example: false
type: boolean
type: object
handlers.CreateCompanyReq:
properties:
@ -508,6 +531,9 @@ definitions:
type: object
handlers.CreateManagerReq:
properties:
company_id:
example: 1
type: integer
email:
example: john.doe@example.com
type: string
@ -663,7 +689,6 @@ definitions:
example: Doe
type: string
otp:
description: Role string
example: "123456"
type: string
password:
@ -688,13 +713,21 @@ definitions:
handlers.ResetPasswordReq:
properties:
email:
example: john.doe@example.com
type: string
otp:
example: "123456"
type: string
password:
example: newpassword123
minLength: 8
type: string
phoneNumber:
phone_number:
example: "1234567890"
type: string
required:
- otp
- password
type: object
handlers.SearchUserByNameOrPhoneReq:
properties:
@ -757,6 +790,9 @@ definitions:
id:
example: 1
type: integer
number_of_outcomes:
example: 1
type: integer
payment_option:
allOf:
- $ref: '#/definitions/domain.PaymentOption'
@ -839,6 +875,8 @@ definitions:
type: string
id:
type: integer
last_login:
type: string
last_name:
type: string
phone_number:
@ -997,6 +1035,75 @@ info:
title: FortuneBet API
version: "1.0"
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:
post:
consumes:
@ -1608,6 +1715,11 @@ paths:
- application/json
description: Update cashier
parameters:
- description: Cashier ID
in: path
name: id
required: true
type: integer
- description: Update cashier
in: body
name: cashier
@ -1882,7 +1994,7 @@ paths:
post:
consumes:
- application/json
description: Create Managers
description: Create Manager
parameters:
- description: Create manager
in: body
@ -1909,7 +2021,7 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Create Managers
summary: Create Manager
tags:
- manager
/managers/{id}:
@ -2291,6 +2403,31 @@ paths:
summary: Search branches
tags:
- 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:
get:
consumes:
@ -2840,6 +2977,39 @@ paths:
summary: Send reset code
tags:
- 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:
get:
consumes:

View File

@ -36,7 +36,8 @@ func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshToken
}
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
`
@ -54,9 +55,31 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token string) (RefreshTok
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
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
WHERE email = $1 OR phone_number = $2
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
WHERE email = $1
OR phone_number = $2
`
type GetUserByEmailPhoneParams struct {
@ -79,6 +102,7 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
&i.PhoneVerified,
&i.CreatedAt,
&i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt,
&i.Suspended,
&i.ReferralCode,

View File

@ -191,7 +191,7 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
}
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
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.CreatedAt,
&i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt,
&i.Suspended,
&i.ReferralCode,
@ -430,7 +431,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
}
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
JOIN users ON branch_cashiers.user_id = users.id
WHERE branch_cashiers.branch_id = $1
@ -457,6 +458,7 @@ func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]Us
&i.PhoneVerified,
&i.CreatedAt,
&i.UpdatedAt,
&i.CompanyID,
&i.SuspendedAt,
&i.Suspended,
&i.ReferralCode,

View File

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

View File

@ -145,6 +145,15 @@ type BranchOperation struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type CompaniesWithWallet struct {
ID int64 `json:"id"`
Name string `json:"name"`
AdminID int64 `json:"admin_id"`
WalletID int64 `json:"wallet_id"`
Balance int64 `json:"balance"`
IsActive bool `json:"is_active"`
}
type Company struct {
ID int64 `json:"id"`
Name string `json:"name"`
@ -330,6 +339,7 @@ type Transaction struct {
BranchID int64 `json:"branch_id"`
CashierID int64 `json:"cashier_id"`
BetID int64 `json:"bet_id"`
NumberOfOutcomes int64 `json:"number_of_outcomes"`
Type int64 `json:"type"`
PaymentOption int64 `json:"payment_option"`
FullName string `json:"full_name"`
@ -356,6 +366,7 @@ type User struct {
PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
CompanyID pgtype.Int8 `json:"company_id"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
Suspended bool `json:"suspended"`
ReferralCode pgtype.Text `json:"referral_code"`

View File

@ -10,7 +10,7 @@ import (
)
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 {
@ -52,6 +52,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
&i.BranchID,
&i.CashierID,
&i.BetID,
&i.NumberOfOutcomes,
&i.Type,
&i.PaymentOption,
&i.FullName,
@ -69,7 +70,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
}
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) {
@ -87,6 +88,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
&i.BranchID,
&i.CashierID,
&i.BetID,
&i.NumberOfOutcomes,
&i.Type,
&i.PaymentOption,
&i.FullName,
@ -111,7 +113,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
}
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) {
@ -129,6 +131,7 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
&i.BranchID,
&i.CashierID,
&i.BetID,
&i.NumberOfOutcomes,
&i.Type,
&i.PaymentOption,
&i.FullName,
@ -153,7 +156,7 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
}
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) {
@ -165,6 +168,7 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction
&i.BranchID,
&i.CashierID,
&i.BetID,
&i.NumberOfOutcomes,
&i.Type,
&i.PaymentOption,
&i.FullName,

View File

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

5
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // 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/go-openapi/jsonpointer v0.21.1 // 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-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gofiber/contrib/websocket v1.3.4
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
@ -44,7 +45,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/savsgio/gotils v0.0.0-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/twitchyliquid64/golang-asm v0.15.1 // 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/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.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -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/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
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.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
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/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-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=

View File

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

View File

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

View File

@ -24,6 +24,7 @@ type Transaction struct {
BranchID int64
CashierID int64
BetID int64
NumberOfOutcomes int64
Type TransactionType
PaymentOption PaymentOption
FullName string
@ -42,6 +43,7 @@ type CreateTransaction struct {
BranchID int64
CashierID int64
BetID int64
NumberOfOutcomes int64
Type TransactionType
PaymentOption PaymentOption
FullName string

View File

@ -27,7 +27,7 @@ type User struct {
SuspendedAt time.Time
Suspended bool
//
BranchID int64
CompanyID ValidInt64
}
type RegisterUserReq struct {
FirstName string
@ -47,6 +47,8 @@ type CreateUserReq struct {
PhoneNumber string
Password string
Role string
Suspended bool
CompanyID ValidInt64
}
type ResetPasswordReq struct {
Email string

View File

@ -36,6 +36,16 @@ func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (d
PhoneNumber: user.PhoneNumber.String,
Password: user.Password,
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
}
@ -72,6 +82,25 @@ func (s *Store) GetRefreshToken(ctx context.Context, token string) (domain.Refre
Revoked: rf.Revoked,
}, 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 {
return s.queries.RevokeRefreshToken(ctx, token)
}

View File

@ -5,6 +5,7 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
)
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) {
dbCompany, err := s.queries.CreateCompany(ctx, convertCreateCompany(company))
if err != nil {
@ -32,27 +44,44 @@ func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany)
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)
if err != nil {
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 {
companies = append(companies, convertDBCompany(dbCompany))
companies = append(companies, convertDBCompanyWithWallet(dbCompany))
}
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)
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) {

View File

@ -13,6 +13,7 @@ func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction {
BranchID: transaction.BranchID,
CashierID: transaction.CashierID,
BetID: transaction.BetID,
NumberOfOutcomes: transaction.NumberOfOutcomes,
Type: domain.TransactionType(transaction.Type),
PaymentOption: domain.PaymentOption(transaction.PaymentOption),
FullName: transaction.FullName,

View File

@ -12,7 +12,7 @@ import (
"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{
ID: usedOtpId,
UsedAt: pgtype.Timestamptz{
@ -83,10 +83,18 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
Suspended: user.Suspended,
}, nil
}
func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, error) {
users, err := s.queries.GetAllUsers(ctx)
func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, int64, error) {
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 {
return nil, err
return nil, 0, err
}
userList := make([]domain.User, len(users))
for i, user := range users {
@ -95,11 +103,24 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
EmailVerified: user.EmailVerified,
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) {
@ -116,6 +137,12 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended,
}
}
return userList, nil
@ -135,6 +162,12 @@ func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]doma
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended,
}
}
return userList, nil
@ -158,6 +191,12 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
})
}
return userList, nil
@ -222,6 +261,12 @@ func (s *Store) GetUserByEmail(ctx context.Context, email string) (domain.User,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}, nil
}
func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) {
@ -235,6 +280,7 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.Use
}
return domain.User{}, err
}
return domain.User{
ID: user.ID,
FirstName: user.FirstName,
@ -242,6 +288,12 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.Use
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}, nil
}
@ -272,7 +324,7 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password
}
return nil
}
func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) {
func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error) {
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
FirstName: user.FirstName,
LastName: user.LastName,
@ -296,6 +348,11 @@ func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (dom
Time: time.Now(),
Valid: true,
},
Suspended: user.Suspended,
CompanyID: pgtype.Int8{
Int64: user.CompanyID.Value,
Valid: user.CompanyID.Valid,
},
})
if err != nil {
return domain.User{}, err
@ -307,5 +364,10 @@ func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (dom
Email: userRes.Email.String,
PhoneNumber: userRes.PhoneNumber.String,
Role: domain.Role(userRes.Role),
EmailVerified: userRes.EmailVerified,
PhoneVerified: userRes.PhoneVerified,
CreatedAt: userRes.CreatedAt.Time,
UpdatedAt: userRes.UpdatedAt.Time,
Suspended: userRes.Suspended,
}, nil
}

View File

@ -16,13 +16,14 @@ var (
ErrUserNotFound = errors.New("user not found")
ErrExpiredToken = errors.New("token expired")
ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again
ErrUserSuspended = errors.New("user has been suspended")
)
type LoginSuccess struct {
UserId int64
Role domain.Role
RfToken string
BranchId int64
CompanyID domain.ValidInt64
}
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 {
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()
if err != nil {
@ -45,6 +63,7 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
})
if err != nil {
return LoginSuccess{}, err
}
@ -52,21 +71,21 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
UserId: user.ID,
Role: user.Role,
RfToken: refreshToken,
BranchId: user.BranchID,
CompanyID: user.CompanyID,
}, 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)
if err != nil {
return err
return domain.RefreshToken{}, err
}
if token.Revoked {
return ErrRefreshTokenNotFound
return domain.RefreshToken{}, ErrRefreshTokenNotFound
}
if token.ExpiresAt.Before(time.Now()) {
return ErrExpiredToken
return domain.RefreshToken{}, ErrExpiredToken
}
// newRefToken, err := generateRefreshToken()
@ -80,8 +99,19 @@ func (s *Service) RefreshToken(ctx context.Context, refToken string) error {
// CreatedAt: time.Now(),
// 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 {
token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
if err != nil {

View File

@ -12,5 +12,6 @@ type UserStore interface {
type TokenStore interface {
CreateRefreshToken(ctx context.Context, rt 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
}

View File

@ -2,6 +2,8 @@ package bet
import (
"context"
"crypto/rand"
"math/big"
"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) {
return s.betStore.CreateBet(ctx, bet)

View File

@ -8,8 +8,9 @@ import (
type CompanyStore interface {
CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error)
GetAllCompanies(ctx context.Context) ([]domain.Company, error)
GetCompanyByID(ctx context.Context, id int64) (domain.Company, error)
GetAllCompanies(ctx context.Context) ([]domain.GetCompany, 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)
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) {
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)
}
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)
}
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) {
return s.companyStore.UpdateCompany(ctx, id, company)
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
"sync"
@ -98,10 +99,18 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
sportIDs := []int{1}
var totalPages int = 1
var page int = 0
// var limit int = 5
// var count int = 0
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 {
page = page + 1
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
log.Printf("📡 Fetching data for event data page %d", page)
resp, err := http.Get(url)
if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
continue
}
defer resp.Body.Close()
@ -109,6 +118,11 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
body, _ := io.ReadAll(resp.Body)
var data struct {
Success int `json:"success"`
Pager struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
}
Results []struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
@ -156,6 +170,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
_ = s.store.SaveUpcomingEvent(ctx, event)
}
totalPages = data.Pager.Total
}
}
return nil

View File

@ -6,7 +6,7 @@ import (
"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
// creator, err := s.userStore.GetUserByID(ctx, createrUserId)
// if err != nil {
@ -33,7 +33,9 @@ func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq) (do
Role: domain.Role(User.Role),
EmailVerified: true,
PhoneVerified: true,
})
Suspended: User.Suspended,
CompanyID: User.CompanyID,
}, is_company)
}
func (s *Service) DeleteUser(ctx context.Context, id int64) error {
@ -43,7 +45,7 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
type Filter struct {
Role string
BranchId ValidBranchId
CompanyID domain.ValidInt64
Page int
PageSize int
}
@ -56,7 +58,7 @@ type ValidBranchId struct {
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
return s.userStore.GetAllUsers(ctx, filter)
}

View File

@ -7,10 +7,10 @@ import (
)
type UserStore interface {
CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error)
CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error)
CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (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)
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)
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, 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,
}
// 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 {
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 (
"errors"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -35,16 +34,6 @@ type loginCustomerRes struct {
// @Failure 500 {object} response.APIResponse
// @Router /auth/login [post]
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
if err := c.BodyParser(&req); err != nil {
@ -62,13 +51,15 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
case errors.Is(err, authentication.ErrUserSuspended):
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
default:
h.logger.Error("Login failed", "error", err)
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 {
h.logger.Error("Failed to create access token", "userID", successRes.UserId, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
@ -100,16 +91,14 @@ type refreshToken struct {
// @Failure 500 {object} response.APIResponse
// @Router /auth/refresh [post]
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 {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Role string `json:"role"`
}
var req refreshTokenReq
var req refreshToken
if err := c.BodyParser(&req); err != nil {
h.logger.Error("Failed to parse RefreshToken request", "error", err)
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)
}
userId := c.Locals("user_id").(int64)
role := c.Locals("role").(string)
branchId := c.Locals("branch_id").(int64)
err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
refreshToken, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
if err != nil {
h.logger.Info("Refresh token attempt failed", "refreshToken", req.RefreshToken, "error", err)
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
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 {
h.logger.Error("Failed to create new access token", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
@ -146,6 +134,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
res := loginCustomerRes{
AccessToken: accessToken,
RefreshToken: req.RefreshToken,
Role: string(user.Role),
}
return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil)
}
@ -153,6 +142,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
// LogOutCustomer godoc
// @Summary Logout customer
// @Description Logout customer
@ -166,9 +156,6 @@ type logoutReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /auth/logout [post]
func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
var req logoutReq
if err := c.BodyParser(&req); err != nil {

View File

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

View File

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

View File

@ -20,6 +20,15 @@ type CompanyRes struct {
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 {
return CompanyRes{
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
// @Summary Create 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)
}
var result []CompanyRes = make([]CompanyRes, 0, len(companies))
var result []GetCompanyRes = make([]GetCompanyRes, 0, len(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)
@ -137,12 +157,43 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
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)
}
// 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
// @Summary Updates a company
// @Description Updates a company

View File

@ -2,8 +2,10 @@ package handlers
import (
"strconv"
"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"
@ -15,11 +17,12 @@ type CreateManagerReq struct {
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"`
}
// CreateManagers godoc
// @Summary Create Managers
// @Description Create Managers
// CreateManager godoc
// @Summary Create Manager
// @Description Create Manager
// @Tags manager
// @Accept json
// @Produce json
@ -30,6 +33,9 @@ type CreateManagerReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /managers [post]
func (h *Handler) CreateManager(c *fiber.Ctx) error {
// Get user_id from middleware
var req CreateManagerReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err)
@ -39,6 +45,22 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
if !ok {
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{
FirstName: req.FirstName,
LastName: req.LastName,
@ -46,16 +68,33 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Role: string(domain.RoleBranchManager),
CompanyID: companyID,
}
_, err := h.userSvc.CreateUser(c.Context(), user)
_, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil {
h.logger.Error("CreateManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create Managers", nil, nil)
h.logger.Error("CreateManager failed", "error", err)
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
// @Summary Get all Managers
// @Description Get all Managers
@ -64,7 +103,7 @@ func (h *Handler) CreateManager(c *fiber.Ctx) error {
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {object} response.APIResponse
// @Success 200 {object} ManagersRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
filter := user.Filter{
Role: string(domain.RoleBranchManager),
BranchId: user.ValidBranchId{
Value: int64(c.QueryInt("branch_id")),
CompanyID: domain.ValidInt64{
Value: int64(c.QueryInt("company_id")),
Valid: true,
},
Page: c.QueryInt("page", 1),
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)
}
Managers, err := h.userSvc.GetAllUsers(c.Context(), filter)
managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil {
h.logger.Error("GetAllManagers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get Managers", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Managers retrieved successfully", Managers, nil)
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 (
"encoding/json"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"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
currentTime := time.Now()
if event.StartTime.Before(currentTime) {
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
}
// currentTime := time.Now()
// if event.StartTime.Before(currentTime) {
// return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
// }
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
@ -184,14 +183,14 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
if err != nil {
h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve ticket")
// h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
}
res := TicketRes{
ID: ticket.ID,
Outcomes: ticket.Outcomes,
Amount: ticket.Amount.Float64(),
Amount: ticket.Amount.Float32(),
TotalOdds: ticket.TotalOdds,
}
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{
ID: ticket.ID,
Outcomes: ticket.Outcomes,
Amount: ticket.Amount.Float64(),
Amount: ticket.Amount.Float32(),
TotalOdds: ticket.TotalOdds,
}
}

View File

@ -14,6 +14,7 @@ type TransactionRes struct {
BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"`
NumberOfOutcomes int64 `json:"number_of_outcomes" example:"1"`
Type int64 `json:"type" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
@ -44,7 +45,7 @@ type CreateTransactionReq struct {
func convertTransaction(transaction domain.Transaction) TransactionRes {
return TransactionRes{
ID: transaction.ID,
Amount: transaction.Amount.Float64(),
Amount: transaction.Amount.Float32(),
BranchID: transaction.BranchID,
CashierID: transaction.CashierID,
BetID: transaction.BetID,
@ -115,11 +116,13 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
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{
BranchID: branchID,
CashierID: userID,
Amount: domain.ToCurrency(req.Amount),
BetID: req.BetID,
NumberOfOutcomes: 1,
Type: domain.TransactionType(req.Type),
PaymentOption: domain.PaymentOption(req.PaymentOption),
FullName: req.FullName,
@ -235,6 +238,7 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"`
}
// UpdateTransactionVerified godoc
// @Summary Updates the verified field of a transaction
// @Description Updates the verified status of a transaction
@ -248,9 +252,6 @@ type UpdateTransactionVerifiedReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /transaction/{id} [patch]
func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"`
}
transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64)

View File

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

View File

@ -2,13 +2,16 @@ package handlers
import (
"errors"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
)
type CheckPhoneEmailExistReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
@ -17,6 +20,15 @@ type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_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
// @Summary 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
// @Router /user/checkPhoneEmailExist [post]
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
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)
}
type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"`
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
// @Summary Send register code
// @Description Send register code
@ -76,10 +86,6 @@ type RegisterCodeReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post]
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
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)
}
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"`
//Role string
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
//
}
// RegisterUser godoc
// @Summary Register user
// @Description Register user
@ -134,15 +139,6 @@ type RegisterUserReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /user/register [post]
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
if err := c.BodyParser(&req); err != nil {
@ -215,8 +211,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type ResetCodeReq struct {
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
// @Summary Send reset code
// @Description Send reset code
@ -229,10 +226,6 @@ type ResetCodeReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post]
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
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)
}
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 {
Email string
PhoneNumber string
Password string
Otp string
}
// ResetPassword godoc
// @Summary Reset password
// @Description Reset password
@ -281,12 +282,6 @@ type ResetPasswordReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post]
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
if err := c.BodyParser(&req); err != nil {
@ -331,6 +326,7 @@ type UserProfileRes struct {
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"`
}
@ -360,6 +356,15 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
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{
ID: user.ID,
FirstName: user.FirstName,
@ -373,6 +378,7 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
}
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
}
@ -404,6 +410,7 @@ type SearchUserByNameOrPhoneReq struct {
// @Failure 500 {object} response.APIResponse
// @Router /user/search [post]
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// TODO: Add filtering by role based on which user is calling this
var req SearchUserByNameOrPhoneReq
if err := c.BodyParser(&req); err != nil {
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))
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{
ID: user.ID,
FirstName: user.FirstName,
@ -438,8 +454,80 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
})
}
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"`
}
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
// @Summary Launch a PopOK virtual game
// @Description Generates a URL to launch a PopOK game
@ -29,15 +39,6 @@ type launchVirtualGameRes struct {
// @Failure 500 {object} response.APIResponse
// @Router /virtual-game/launch [post]
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)
if !ok || userID == 0 {

View File

@ -27,7 +27,7 @@ type WalletRes struct {
func convertWallet(wallet domain.Wallet) WalletRes {
return WalletRes{
ID: wallet.ID,
Balance: wallet.Balance.Float64(),
Balance: wallet.Balance.Float32(),
IsWithdraw: wallet.IsWithdraw,
IsBettable: wallet.IsBettable,
IsTransferable: wallet.IsTransferable,
@ -55,9 +55,9 @@ func convertCustomerWallet(wallet domain.GetCustomerWallet) CustomerWalletRes {
return CustomerWalletRes{
ID: wallet.ID,
RegularID: wallet.RegularID,
RegularBalance: wallet.RegularBalance.Float64(),
RegularBalance: wallet.RegularBalance.Float32(),
StaticID: wallet.StaticID,
StaticBalance: wallet.StaticBalance.Float64(),
StaticBalance: wallet.StaticBalance.Float32(),
CustomerID: wallet.CustomerID,
CompanyID: wallet.CompanyID,
RegularUpdatedAt: wallet.RegularUpdatedAt,
@ -162,7 +162,7 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
for _, wallet := range wallets {
res = append(res, BranchWalletRes{
ID: wallet.ID,
Balance: wallet.Balance.Float64(),
Balance: wallet.Balance.Float32(),
IsActive: wallet.IsActive,
Name: wallet.Name,
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
// @Summary 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
// @Router /wallet/{id} [patch]
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)

View File

@ -19,7 +19,7 @@ type UserClaim struct {
jwt.RegisteredClaims
UserId int64
Role domain.Role
BranchId int64
CompanyID domain.ValidInt64
}
type PopOKClaim struct {
@ -37,7 +37,7 @@ type JwtConfig struct {
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{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "github.com/lafetz/snippitstash",
@ -48,7 +48,7 @@ func CreateJwt(userId int64, Role domain.Role, BranchId int64, key string, expir
},
UserId: userId,
Role: Role,
BranchId: BranchId,
CompanyID: CompanyID,
})
jwtToken, err := token.SignedString([]byte(key))
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("role", claim.Role)
c.Locals("branch_id", claim.BranchId)
c.Locals("company_id", claim.CompanyID)
c.Locals("refresh_token", refreshToken)
return c.Next()

View File

@ -7,6 +7,7 @@ import (
_ "github.com/SamuelTariku/FortuneBet-Backend/docs"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/handlers"
"github.com/gofiber/fiber/v2"
fiberSwagger "github.com/swaggo/fiber-swagger"
)
@ -33,7 +34,7 @@ func (a *App) initAppRoutes() {
// Auth Routes
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.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
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/checkPhoneEmailExist", h.CheckPhoneEmailExist)
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.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.Post("/cashiers", a.authMiddleware, h.CreateCashier)
a.fiber.Put("/cashiers/:id", a.authMiddleware, h.UpdateCashier)
//
a.fiber.Get("/admin", a.authMiddleware, h.GetAllAdmins)
a.fiber.Post("/admin", a.authMiddleware, h.CreateAdmin)
a.fiber.Get("/managers", a.authMiddleware, h.GetAllManagers)
a.fiber.Post("/managers", a.authMiddleware, h.CreateManager)
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
a.fiber.Get("/company/:id/branch", a.authMiddleware, h.GetBranchByCompanyID)
a.fiber.Get("/prematch/odds/:event_id", h.GetPrematchOdds)
a.fiber.Get("/prematch/odds", h.GetALLPrematchOdds)
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.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany)
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
a.fiber.Post("/ticket", h.CreateTicket)
@ -132,12 +136,12 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/ticket/:id", h.GetTicketByID)
// Bet Routes
a.fiber.Post("/bet", h.CreateBet)
a.fiber.Get("/bet", h.GetAllBet)
a.fiber.Get("/bet/:id", h.GetBetByID)
a.fiber.Post("/bet", a.authMiddleware, h.CreateBet)
a.fiber.Get("/bet", a.authMiddleware, h.GetAllBet)
a.fiber.Get("/bet/:id", a.authMiddleware, h.GetBetByID)
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)
a.fiber.Patch("/bet/:id", h.UpdateCashOut)
a.fiber.Delete("/bet/:id", h.DeleteBet)
a.fiber.Patch("/bet/:id", a.authMiddleware, h.UpdateCashOut)
a.fiber.Delete("/bet/:id", a.authMiddleware, h.DeleteBet)
// Wallet
a.fiber.Get("/wallet", h.GetAllWallets)
@ -152,10 +156,10 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
// Transactions /transactions
a.fiber.Post("/transaction", h.CreateTransaction)
a.fiber.Get("/transaction", h.GetAllTransactions)
a.fiber.Get("/transaction/:id", h.GetTransactionByID)
a.fiber.Patch("/transaction/:id", h.UpdateTransactionVerified)
a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction)
a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions)
a.fiber.Get("/transaction/:id", a.authMiddleware, h.GetTransactionByID)
a.fiber.Patch("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
// Notification Routes
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)