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,16 +1,21 @@
-- 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
UPDATE refresh_tokens
SET revoked = TRUE
WHERE token = $1;

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,
@ -88,8 +112,8 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
}
const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
UPDATE refresh_tokens
SET revoked = TRUE
UPDATE refresh_tokens
SET revoked = TRUE
WHERE token = $1
`

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"`
@ -325,23 +334,24 @@ type TicketWithOutcome struct {
}
type Transaction struct {
ID int64 `json:"id"`
Amount int64 `json:"amount"`
BranchID int64 `json:"branch_id"`
CashierID int64 `json:"cashier_id"`
BetID int64 `json:"bet_id"`
Type int64 `json:"type"`
PaymentOption int64 `json:"payment_option"`
FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
ID int64 `json:"id"`
Amount int64 `json:"amount"`
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"`
PhoneNumber string `json:"phone_number"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type User struct {
@ -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

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

View File

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

View File

@ -29,13 +29,23 @@ func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (d
return domain.User{}, err
}
return domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Password: user.Password,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
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

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

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,23 +83,44 @@ 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 {
userList[i] = domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
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) {
@ -110,12 +131,18 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.User, error) {
userList := make([]domain.User, len(users))
for i, user := range users {
userList[i] = domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended,
}
}
return userList, nil
@ -129,12 +156,18 @@ func (s *Store) GetCashiersByBranch(ctx context.Context, branchID int64) ([]doma
userList := make([]domain.User, len(users))
for i, user := range users {
userList[i] = domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended,
}
}
return userList, nil
@ -152,12 +185,18 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, searchString string
userList := make([]domain.User, 0, len(users))
for _, user := range users {
userList = append(userList, domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
})
}
return userList, nil
@ -216,12 +255,18 @@ func (s *Store) GetUserByEmail(ctx context.Context, email string) (domain.User,
return domain.User{}, err
}
return domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
Suspended: user.Suspended,
SuspendedAt: user.SuspendedAt.Time,
}, nil
}
func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) {
@ -235,13 +280,20 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.Use
}
return domain.User{}, err
}
return domain.User{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email.String,
PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(user.Role),
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
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,16 +348,26 @@ 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
}
return domain.User{
ID: userRes.ID,
FirstName: userRes.FirstName,
LastName: userRes.LastName,
Email: userRes.Email.String,
PhoneNumber: userRes.PhoneNumber.String,
Role: domain.Role(userRes.Role),
ID: userRes.ID,
FirstName: userRes.FirstName,
LastName: userRes.LastName,
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
UserId int64
Role domain.Role
RfToken string
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,28 +63,29 @@ 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
}
return LoginSuccess{
UserId: user.ID,
Role: user.Role,
RfToken: refreshToken,
BranchId: user.BranchID,
UserId: user.ID,
Role: user.Role,
RfToken: refreshToken,
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,63 +99,78 @@ 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)
resp, err := http.Get(url)
if err != nil {
continue
}
defer resp.Body.Close()
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()
body, _ := io.ReadAll(resp.Body)
var data struct {
Success int `json:"success"`
Results []struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"home"`
Away *struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
continue
}
for _, ev := range data.Results {
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
event := domain.UpcomingEvent{
ID: ev.ID,
SportID: ev.SportID,
MatchName: ev.Home.Name,
HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID,
AwayTeamID: "",
HomeKitImage: "",
AwayKitImage: "",
LeagueID: ev.League.ID,
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
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"`
Time string `json:"time"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"home"`
Away *struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
continue
}
if ev.Away != nil {
event.AwayTeam = ev.Away.Name
event.AwayTeamID = ev.Away.ID
}
for _, ev := range data.Results {
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
event := domain.UpcomingEvent{
ID: ev.ID,
SportID: ev.SportID,
MatchName: ev.Home.Name,
HomeTeam: ev.Home.Name,
AwayTeam: "", // handle nil safely
HomeTeamID: ev.Home.ID,
AwayTeamID: "",
HomeKitImage: "",
AwayKitImage: "",
LeagueID: ev.League.ID,
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
}
_ = s.store.SaveUpcomingEvent(ctx, event)
if ev.Away != nil {
event.AwayTeam = ev.Away.Name
event.AwayTeamID = ev.Away.ID
}
_ = s.store.SaveUpcomingEvent(ctx, event)
}
totalPages = data.Pager.Total
}
}

View File

@ -6,7 +6,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
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 {
@ -42,10 +44,10 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
}
type Filter struct {
Role string
BranchId ValidBranchId
Page int
PageSize int
Role string
CompanyID domain.ValidInt64
Page int
PageSize int
}
type ValidRole struct {
Value domain.Role
@ -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,13 +134,15 @@ 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)
}
type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
// LogOutCustomer godoc
// @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

@ -9,21 +9,22 @@ import (
)
type TransactionRes struct {
ID int64 `json:"id" example:"1"`
Amount float32 `json:"amount" example:"100.0"`
BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"`
Type int64 `json:"type" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified" example:"true"`
ID int64 `json:"id" example:"1"`
Amount float32 `json:"amount" example:"100.0"`
BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"`
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"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified" example:"true"`
}
type CreateTransactionReq struct {
@ -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,20 +116,22 @@ 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,
Type: domain.TransactionType(req.Type),
PaymentOption: domain.PaymentOption(req.PaymentOption),
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BankCode: req.BankCode,
BeneficiaryName: req.BeneficiaryName,
AccountName: req.AccountName,
AccountNumber: req.AccountNumber,
ReferenceNumber: req.ReferenceNumber,
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,
PhoneNumber: req.PhoneNumber,
BankCode: req.BankCode,
BeneficiaryName: req.BeneficiaryName,
AccountName: req.AccountName,
AccountNumber: req.AccountNumber,
ReferenceNumber: req.ReferenceNumber,
})
if err != nil {
@ -233,8 +236,9 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
}
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"`
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

@ -17,9 +17,9 @@ var (
type UserClaim struct {
jwt.RegisteredClaims
UserId int64
Role domain.Role
BranchId int64
UserId int64
Role domain.Role
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",
@ -46,9 +46,9 @@ func CreateJwt(userId int64, Role domain.Role, BranchId int64, key string, expir
NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)),
},
UserId: userId,
Role: Role,
BranchId: BranchId,
UserId: userId,
Role: Role,
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)