integration fixes

This commit is contained in:
Samuel Tariku 2025-04-26 14:48:45 +03:00
parent fcd926223a
commit 208a2d74be
45 changed files with 2905 additions and 405 deletions

View File

@ -10,8 +10,9 @@ CREATE TABLE IF NOT EXISTS users (
phone_verified BOOLEAN NOT NULL DEFAULT FALSE, phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, updated_at TIMESTAMPTZ,
-- company_id BIGINT,
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended suspended_at TIMESTAMPTZ NULL,
-- this can be NULL if the user is not suspended
suspended BOOLEAN NOT NULL DEFAULT FALSE, suspended BOOLEAN NOT NULL DEFAULT FALSE,
CHECK ( CHECK (
email IS NOT NULL email IS NOT NULL
@ -53,6 +54,7 @@ CREATE TABLE IF NOT EXISTS bets (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_shop_bet BOOLEAN NOT NULL, is_shop_bet BOOLEAN NOT NULL,
UNIQUE(cashout_id),
CHECK ( CHECK (
user_id IS NOT NULL user_id IS NOT NULL
OR branch_id IS NOT NULL OR branch_id IS NOT NULL
@ -148,6 +150,7 @@ CREATE TABLE IF NOT EXISTS transactions (
branch_id BIGINT NOT NULL, branch_id BIGINT NOT NULL,
cashier_id BIGINT NOT NULL, cashier_id BIGINT NOT NULL,
bet_id BIGINT NOT NULL, bet_id BIGINT NOT NULL,
number_of_outcomes BIGINT NOT NULL,
type BIGINT NOT NULL, type BIGINT NOT NULL,
payment_option BIGINT NOT NULL, payment_option BIGINT NOT NULL,
full_name VARCHAR(255) NOT NULL, full_name VARCHAR(255) NOT NULL,

View File

@ -1,5 +1,4 @@
CREATE TYPE ReferralStatus AS ENUM ('PENDING', 'COMPLETED', 'EXPIRED', 'CANCELLED'); CREATE TYPE ReferralStatus AS ENUM ('PENDING', 'COMPLETED', 'EXPIRED', 'CANCELLED');
CREATE TABLE IF NOT EXISTS referral_settings ( CREATE TABLE IF NOT EXISTS referral_settings (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
referral_reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, referral_reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
@ -17,7 +16,6 @@ CREATE TABLE IF NOT EXISTS referral_settings (
AND cashback_percentage <= 100 AND cashback_percentage <= 100
) )
); );
CREATE TABLE IF NOT EXISTS referrals ( CREATE TABLE IF NOT EXISTS referrals (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
referral_code VARCHAR(10) NOT NULL UNIQUE, referral_code VARCHAR(10) NOT NULL UNIQUE,
@ -29,25 +27,20 @@ CREATE TABLE IF NOT EXISTS referrals (
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ NOT NULL,
FOREIGN KEY (referrer_id) REFERENCES users (id), -- FOREIGN KEY (referrer_id) REFERENCES users (id),
FOREIGN KEY (referred_id) REFERENCES users (id), -- FOREIGN KEY (referred_id) REFERENCES users (id),
CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0), CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0),
CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0) CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0)
); );
CREATE INDEX idx_referrals_referral_code ON referrals (referral_code); CREATE INDEX idx_referrals_referral_code ON referrals (referral_code);
CREATE INDEX idx_referrals_referrer_id ON referrals (referrer_id); CREATE INDEX idx_referrals_referrer_id ON referrals (referrer_id);
CREATE INDEX idx_referrals_status ON referrals (status); CREATE INDEX idx_referrals_status ON referrals (status);
ALTER TABLE users ALTER TABLE users
ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE, ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE,
ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10); ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10);
-- Modify wallet table to track bonus money separately -- Modify wallet table to track bonus money separately
ALTER TABLE wallets ALTER TABLE wallets
ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
ADD CONSTRAINT bonus_balance_positive CHECK (bonus_balance >= 0), ADD CONSTRAINT bonus_balance_positive CHECK (bonus_balance >= 0),
ADD CONSTRAINT cash_balance_positive CHECK (cash_balance >= 0); ADD CONSTRAINT cash_balance_positive CHECK (cash_balance >= 0);

View File

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

View File

@ -13,6 +13,10 @@ FROM companies;
SELECT * SELECT *
FROM companies FROM companies
WHERE id = $1; WHERE id = $1;
-- name: SearchCompanyByName :many
SELECT *
FROM companies
WHERE name ILIKE '%' || $1 || '%';
-- name: UpdateCompany :one -- name: UpdateCompany :one
UPDATE companies UPDATE companies
SET name = $1, SET name = $1,

View File

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

View File

@ -24,6 +24,111 @@ const docTemplate = `{
"host": "{{.Host}}", "host": "{{.Host}}",
"basePath": "{{.BasePath}}", "basePath": "{{.BasePath}}",
"paths": { "paths": {
"/admin": {
"get": {
"description": "Get all Admins",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Get all Admins",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"post": {
"description": "Create Admin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Create Admin",
"parameters": [
{
"description": "Create admin",
"name": "manger",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.CreateAdminReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/auth/login": { "/auth/login": {
"post": { "post": {
"description": "Login customer", "description": "Login customer",
@ -955,6 +1060,13 @@ const docTemplate = `{
], ],
"summary": "Update cashier", "summary": "Update cashier",
"parameters": [ "parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{ {
"description": "Update cashier", "description": "Update cashier",
"name": "cashier", "name": "cashier",
@ -1361,7 +1473,7 @@ const docTemplate = `{
} }
}, },
"post": { "post": {
"description": "Create Managers", "description": "Create Manager",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1371,7 +1483,7 @@ const docTemplate = `{
"tags": [ "tags": [
"manager" "manager"
], ],
"summary": "Create Managers", "summary": "Create Manager",
"parameters": [ "parameters": [
{ {
"description": "Create manager", "description": "Create manager",
@ -1803,6 +1915,147 @@ const docTemplate = `{
} }
} }
}, },
"/referral/settings": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Retrieves current referral settings (admin only)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Get referral settings",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.ReferralSettings"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Updates referral settings (admin only)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Update referral settings",
"parameters": [
{
"description": "Referral settings",
"name": "settings",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.ReferralSettings"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/referral/stats": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Retrieves referral statistics for the authenticated user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Get referral statistics",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.ReferralStats"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/search/branch": { "/search/branch": {
"get": { "get": {
"description": "Search branches by name or location", "description": "Search branches by name or location",
@ -1850,6 +2103,44 @@ const docTemplate = `{
} }
} }
}, },
"/search/company": {
"get": {
"description": "Gets all companies",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"company"
],
"summary": "Gets all companies",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.CompanyRes"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/supportedOperation": { "/supportedOperation": {
"get": { "get": {
"description": "Gets all supported operations", "description": "Gets all supported operations",
@ -2184,7 +2475,7 @@ const docTemplate = `{
} }
}, },
"patch": { "patch": {
"description": "Updates the cashed out field", "description": "Updates the verified status of a transaction",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2194,7 +2485,7 @@ const docTemplate = `{
"tags": [ "tags": [
"transaction" "transaction"
], ],
"summary": "Updates the cashed out field", "summary": "Updates the verified field of a transaction",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@ -2205,7 +2496,7 @@ const docTemplate = `{
}, },
{ {
"description": "Updates Transaction Verification", "description": "Updates Transaction Verification",
"name": "updateCashOut", "name": "updateVerified",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
@ -2689,6 +2980,56 @@ const docTemplate = `{
} }
} }
}, },
"/user/single/{id}": {
"get": {
"description": "Get a single user by id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Get user by id",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/wallet": { "/user/wallet": {
"get": { "get": {
"security": [ "security": [
@ -2738,6 +3079,109 @@ const docTemplate = `{
} }
} }
}, },
"/virtual-game/callback": {
"post": {
"description": "Processes callbacks from PopOK for game events",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"virtual-game"
],
"summary": "Handle PopOK game callback",
"parameters": [
{
"description": "Callback data",
"name": "callback",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.PopOKCallback"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/virtual-game/launch": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Generates a URL to launch a PopOK game",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"virtual-game"
],
"summary": "Launch a PopOK virtual game",
"parameters": [
{
"description": "Game launch details",
"name": "launch",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.launchVirtualGameReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.launchVirtualGameRes"
}
},
"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"
}
}
}
}
},
"/wallet": { "/wallet": {
"get": { "get": {
"description": "Retrieve all wallets", "description": "Retrieve all wallets",
@ -3019,6 +3463,34 @@ const docTemplate = `{
"BANK" "BANK"
] ]
}, },
"domain.PopOKCallback": {
"type": "object",
"properties": {
"amount": {
"type": "number"
},
"currency": {
"type": "string"
},
"session_id": {
"type": "string"
},
"signature": {
"description": "HMAC-SHA256 signature for verification",
"type": "string"
},
"timestamp": {
"type": "integer"
},
"transaction_id": {
"type": "string"
},
"type": {
"description": "BET, WIN, REFUND, JACKPOT_WIN",
"type": "string"
}
}
},
"domain.RawOddsByMarketID": { "domain.RawOddsByMarketID": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3040,6 +3512,58 @@ const docTemplate = `{
} }
} }
}, },
"domain.ReferralSettings": {
"type": "object",
"properties": {
"betReferralBonusPercentage": {
"type": "number"
},
"cashbackPercentage": {
"type": "number"
},
"createdAt": {
"type": "string"
},
"expiresAfterDays": {
"type": "integer"
},
"id": {
"type": "integer"
},
"maxReferrals": {
"type": "integer"
},
"referralRewardAmount": {
"type": "number"
},
"updatedAt": {
"type": "string"
},
"updatedBy": {
"type": "string"
},
"version": {
"type": "integer"
}
}
},
"domain.ReferralStats": {
"type": "object",
"properties": {
"completedReferrals": {
"type": "integer"
},
"pendingRewards": {
"type": "number"
},
"totalReferrals": {
"type": "integer"
},
"totalRewardEarned": {
"type": "number"
}
}
},
"domain.Role": { "domain.Role": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -3327,6 +3851,9 @@ const docTemplate = `{
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3370,11 +3897,39 @@ const docTemplate = `{
} }
} }
}, },
"handlers.CreateAdminReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
},
"first_name": {
"type": "string",
"example": "John"
},
"last_name": {
"type": "string",
"example": "Doe"
},
"password": {
"type": "string",
"example": "password123"
},
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"event_id": { "event_id": {
"description": "BetID int64 ` + "`" + `json:\"bet_id\" example:\"1\"` + "`" + `",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
@ -3399,10 +3954,6 @@ const docTemplate = `{
"type": "string", "type": "string",
"example": "John" "example": "John"
}, },
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": { "outcomes": {
"type": "array", "type": "array",
"items": { "items": {
@ -3497,6 +4048,10 @@ const docTemplate = `{
"phone_number": { "phone_number": {
"type": "string", "type": "string",
"example": "1234567890" "example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
} }
} }
}, },
@ -3516,6 +4071,10 @@ const docTemplate = `{
"handlers.CreateManagerReq": { "handlers.CreateManagerReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": { "email": {
"type": "string", "type": "string",
"example": "john.doe@example.com" "example": "john.doe@example.com"
@ -3737,7 +4296,6 @@ const docTemplate = `{
"example": "Doe" "example": "Doe"
}, },
"otp": { "otp": {
"description": "Role string",
"type": "string", "type": "string",
"example": "123456" "example": "123456"
}, },
@ -3770,18 +4328,27 @@ const docTemplate = `{
}, },
"handlers.ResetPasswordReq": { "handlers.ResetPasswordReq": {
"type": "object", "type": "object",
"required": [
"otp",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string" "type": "string",
"example": "john.doe@example.com"
}, },
"otp": { "otp": {
"type": "string" "type": "string",
"example": "123456"
}, },
"password": { "password": {
"type": "string" "type": "string",
"minLength": 8,
"example": "newpassword123"
}, },
"phoneNumber": { "phone_number": {
"type": "string" "type": "string",
"example": "1234567890"
} }
} }
}, },
@ -3872,6 +4439,10 @@ const docTemplate = `{
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": { "payment_option": {
"allOf": [ "allOf": [
{ {
@ -3952,17 +4523,25 @@ const docTemplate = `{
}, },
"handlers.UpdateTransactionVerifiedReq": { "handlers.UpdateTransactionVerifiedReq": {
"type": "object", "type": "object",
"required": [
"verified"
],
"properties": { "properties": {
"verified": { "verified": {
"type": "boolean" "type": "boolean",
"example": true
} }
} }
}, },
"handlers.UpdateWalletActiveReq": { "handlers.UpdateWalletActiveReq": {
"type": "object", "type": "object",
"required": [
"is_active"
],
"properties": { "properties": {
"isActive": { "is_active": {
"type": "boolean" "type": "boolean",
"example": true
} }
} }
}, },
@ -3984,6 +4563,9 @@ const docTemplate = `{
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"last_login": {
"type": "string"
},
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },
@ -4046,8 +4628,45 @@ const docTemplate = `{
} }
} }
}, },
"handlers.launchVirtualGameReq": {
"type": "object",
"required": [
"currency",
"game_id",
"mode"
],
"properties": {
"currency": {
"type": "string",
"example": "USD"
},
"game_id": {
"type": "string",
"example": "crash_001"
},
"mode": {
"type": "string",
"enum": [
"REAL",
"DEMO"
],
"example": "REAL"
}
}
},
"handlers.launchVirtualGameRes": {
"type": "object",
"properties": {
"launch_url": {
"type": "string"
}
}
},
"handlers.loginCustomerReq": { "handlers.loginCustomerReq": {
"type": "object", "type": "object",
"required": [
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -4079,20 +4698,30 @@ const docTemplate = `{
}, },
"handlers.logoutReq": { "handlers.logoutReq": {
"type": "object", "type": "object",
"required": [
"refresh_token"
],
"properties": { "properties": {
"refresh_token": { "refresh_token": {
"type": "string" "type": "string",
"example": "\u003crefresh-token\u003e"
} }
} }
}, },
"handlers.refreshToken": { "handlers.refreshToken": {
"type": "object", "type": "object",
"required": [
"access_token",
"refresh_token"
],
"properties": { "properties": {
"access_token": { "access_token": {
"type": "string" "type": "string",
"example": "\u003cjwt-token\u003e"
}, },
"refresh_token": { "refresh_token": {
"type": "string" "type": "string",
"example": "\u003crefresh-token\u003e"
} }
} }
}, },

View File

@ -16,6 +16,111 @@
"version": "1.0" "version": "1.0"
}, },
"paths": { "paths": {
"/admin": {
"get": {
"description": "Get all Admins",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Get all Admins",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"post": {
"description": "Create Admin",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Create Admin",
"parameters": [
{
"description": "Create admin",
"name": "manger",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.CreateAdminReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/auth/login": { "/auth/login": {
"post": { "post": {
"description": "Login customer", "description": "Login customer",
@ -947,6 +1052,13 @@
], ],
"summary": "Update cashier", "summary": "Update cashier",
"parameters": [ "parameters": [
{
"type": "integer",
"description": "Cashier ID",
"name": "id",
"in": "path",
"required": true
},
{ {
"description": "Update cashier", "description": "Update cashier",
"name": "cashier", "name": "cashier",
@ -1353,7 +1465,7 @@
} }
}, },
"post": { "post": {
"description": "Create Managers", "description": "Create Manager",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1363,7 +1475,7 @@
"tags": [ "tags": [
"manager" "manager"
], ],
"summary": "Create Managers", "summary": "Create Manager",
"parameters": [ "parameters": [
{ {
"description": "Create manager", "description": "Create manager",
@ -1795,6 +1907,147 @@
} }
} }
}, },
"/referral/settings": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Retrieves current referral settings (admin only)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Get referral settings",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.ReferralSettings"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Updates referral settings (admin only)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Update referral settings",
"parameters": [
{
"description": "Referral settings",
"name": "settings",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.ReferralSettings"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/referral/stats": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Retrieves referral statistics for the authenticated user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"referral"
],
"summary": "Get referral statistics",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.ReferralStats"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/search/branch": { "/search/branch": {
"get": { "get": {
"description": "Search branches by name or location", "description": "Search branches by name or location",
@ -1842,6 +2095,44 @@
} }
} }
}, },
"/search/company": {
"get": {
"description": "Gets all companies",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"company"
],
"summary": "Gets all companies",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.CompanyRes"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/supportedOperation": { "/supportedOperation": {
"get": { "get": {
"description": "Gets all supported operations", "description": "Gets all supported operations",
@ -2176,7 +2467,7 @@
} }
}, },
"patch": { "patch": {
"description": "Updates the cashed out field", "description": "Updates the verified status of a transaction",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2186,7 +2477,7 @@
"tags": [ "tags": [
"transaction" "transaction"
], ],
"summary": "Updates the cashed out field", "summary": "Updates the verified field of a transaction",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "integer",
@ -2197,7 +2488,7 @@
}, },
{ {
"description": "Updates Transaction Verification", "description": "Updates Transaction Verification",
"name": "updateCashOut", "name": "updateVerified",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
@ -2681,6 +2972,56 @@
} }
} }
}, },
"/user/single/{id}": {
"get": {
"description": "Get a single user by id",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Get user by id",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/wallet": { "/user/wallet": {
"get": { "get": {
"security": [ "security": [
@ -2730,6 +3071,109 @@
} }
} }
}, },
"/virtual-game/callback": {
"post": {
"description": "Processes callbacks from PopOK for game events",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"virtual-game"
],
"summary": "Handle PopOK game callback",
"parameters": [
{
"description": "Callback data",
"name": "callback",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.PopOKCallback"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/virtual-game/launch": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Generates a URL to launch a PopOK game",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"virtual-game"
],
"summary": "Launch a PopOK virtual game",
"parameters": [
{
"description": "Game launch details",
"name": "launch",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.launchVirtualGameReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.launchVirtualGameRes"
}
},
"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"
}
}
}
}
},
"/wallet": { "/wallet": {
"get": { "get": {
"description": "Retrieve all wallets", "description": "Retrieve all wallets",
@ -3011,6 +3455,34 @@
"BANK" "BANK"
] ]
}, },
"domain.PopOKCallback": {
"type": "object",
"properties": {
"amount": {
"type": "number"
},
"currency": {
"type": "string"
},
"session_id": {
"type": "string"
},
"signature": {
"description": "HMAC-SHA256 signature for verification",
"type": "string"
},
"timestamp": {
"type": "integer"
},
"transaction_id": {
"type": "string"
},
"type": {
"description": "BET, WIN, REFUND, JACKPOT_WIN",
"type": "string"
}
}
},
"domain.RawOddsByMarketID": { "domain.RawOddsByMarketID": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3032,6 +3504,58 @@
} }
} }
}, },
"domain.ReferralSettings": {
"type": "object",
"properties": {
"betReferralBonusPercentage": {
"type": "number"
},
"cashbackPercentage": {
"type": "number"
},
"createdAt": {
"type": "string"
},
"expiresAfterDays": {
"type": "integer"
},
"id": {
"type": "integer"
},
"maxReferrals": {
"type": "integer"
},
"referralRewardAmount": {
"type": "number"
},
"updatedAt": {
"type": "string"
},
"updatedBy": {
"type": "string"
},
"version": {
"type": "integer"
}
}
},
"domain.ReferralStats": {
"type": "object",
"properties": {
"completedReferrals": {
"type": "integer"
},
"pendingRewards": {
"type": "number"
},
"totalReferrals": {
"type": "integer"
},
"totalRewardEarned": {
"type": "number"
}
}
},
"domain.Role": { "domain.Role": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -3319,6 +3843,9 @@
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3362,11 +3889,39 @@
} }
} }
}, },
"handlers.CreateAdminReq": {
"type": "object",
"properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": {
"type": "string",
"example": "john.doe@example.com"
},
"first_name": {
"type": "string",
"example": "John"
},
"last_name": {
"type": "string",
"example": "Doe"
},
"password": {
"type": "string",
"example": "password123"
},
"phone_number": {
"type": "string",
"example": "1234567890"
}
}
},
"handlers.CreateBetOutcomeReq": { "handlers.CreateBetOutcomeReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"event_id": { "event_id": {
"description": "BetID int64 `json:\"bet_id\" example:\"1\"`",
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
@ -3391,10 +3946,6 @@
"type": "string", "type": "string",
"example": "John" "example": "John"
}, },
"is_shop_bet": {
"type": "boolean",
"example": false
},
"outcomes": { "outcomes": {
"type": "array", "type": "array",
"items": { "items": {
@ -3489,6 +4040,10 @@
"phone_number": { "phone_number": {
"type": "string", "type": "string",
"example": "1234567890" "example": "1234567890"
},
"suspended": {
"type": "boolean",
"example": false
} }
} }
}, },
@ -3508,6 +4063,10 @@
"handlers.CreateManagerReq": { "handlers.CreateManagerReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"company_id": {
"type": "integer",
"example": 1
},
"email": { "email": {
"type": "string", "type": "string",
"example": "john.doe@example.com" "example": "john.doe@example.com"
@ -3729,7 +4288,6 @@
"example": "Doe" "example": "Doe"
}, },
"otp": { "otp": {
"description": "Role string",
"type": "string", "type": "string",
"example": "123456" "example": "123456"
}, },
@ -3762,18 +4320,27 @@
}, },
"handlers.ResetPasswordReq": { "handlers.ResetPasswordReq": {
"type": "object", "type": "object",
"required": [
"otp",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string" "type": "string",
"example": "john.doe@example.com"
}, },
"otp": { "otp": {
"type": "string" "type": "string",
"example": "123456"
}, },
"password": { "password": {
"type": "string" "type": "string",
"minLength": 8,
"example": "newpassword123"
}, },
"phoneNumber": { "phone_number": {
"type": "string" "type": "string",
"example": "1234567890"
} }
} }
}, },
@ -3864,6 +4431,10 @@
"type": "integer", "type": "integer",
"example": 1 "example": 1
}, },
"number_of_outcomes": {
"type": "integer",
"example": 1
},
"payment_option": { "payment_option": {
"allOf": [ "allOf": [
{ {
@ -3944,17 +4515,25 @@
}, },
"handlers.UpdateTransactionVerifiedReq": { "handlers.UpdateTransactionVerifiedReq": {
"type": "object", "type": "object",
"required": [
"verified"
],
"properties": { "properties": {
"verified": { "verified": {
"type": "boolean" "type": "boolean",
"example": true
} }
} }
}, },
"handlers.UpdateWalletActiveReq": { "handlers.UpdateWalletActiveReq": {
"type": "object", "type": "object",
"required": [
"is_active"
],
"properties": { "properties": {
"isActive": { "is_active": {
"type": "boolean" "type": "boolean",
"example": true
} }
} }
}, },
@ -3976,6 +4555,9 @@
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"last_login": {
"type": "string"
},
"last_name": { "last_name": {
"type": "string" "type": "string"
}, },
@ -4038,8 +4620,45 @@
} }
} }
}, },
"handlers.launchVirtualGameReq": {
"type": "object",
"required": [
"currency",
"game_id",
"mode"
],
"properties": {
"currency": {
"type": "string",
"example": "USD"
},
"game_id": {
"type": "string",
"example": "crash_001"
},
"mode": {
"type": "string",
"enum": [
"REAL",
"DEMO"
],
"example": "REAL"
}
}
},
"handlers.launchVirtualGameRes": {
"type": "object",
"properties": {
"launch_url": {
"type": "string"
}
}
},
"handlers.loginCustomerReq": { "handlers.loginCustomerReq": {
"type": "object", "type": "object",
"required": [
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -4071,20 +4690,30 @@
}, },
"handlers.logoutReq": { "handlers.logoutReq": {
"type": "object", "type": "object",
"required": [
"refresh_token"
],
"properties": { "properties": {
"refresh_token": { "refresh_token": {
"type": "string" "type": "string",
"example": "\u003crefresh-token\u003e"
} }
} }
}, },
"handlers.refreshToken": { "handlers.refreshToken": {
"type": "object", "type": "object",
"required": [
"access_token",
"refresh_token"
],
"properties": { "properties": {
"access_token": { "access_token": {
"type": "string" "type": "string",
"example": "\u003cjwt-token\u003e"
}, },
"refresh_token": { "refresh_token": {
"type": "string" "type": "string",
"example": "\u003crefresh-token\u003e"
} }
} }
}, },

View File

@ -103,6 +103,25 @@ definitions:
- TELEBIRR_TRANSACTION - TELEBIRR_TRANSACTION
- ARIFPAY_TRANSACTION - ARIFPAY_TRANSACTION
- BANK - BANK
domain.PopOKCallback:
properties:
amount:
type: number
currency:
type: string
session_id:
type: string
signature:
description: HMAC-SHA256 signature for verification
type: string
timestamp:
type: integer
transaction_id:
type: string
type:
description: BET, WIN, REFUND, JACKPOT_WIN
type: string
type: object
domain.RawOddsByMarketID: domain.RawOddsByMarketID:
properties: properties:
fetched_at: fetched_at:
@ -117,6 +136,40 @@ definitions:
items: {} items: {}
type: array type: array
type: object type: object
domain.ReferralSettings:
properties:
betReferralBonusPercentage:
type: number
cashbackPercentage:
type: number
createdAt:
type: string
expiresAfterDays:
type: integer
id:
type: integer
maxReferrals:
type: integer
referralRewardAmount:
type: number
updatedAt:
type: string
updatedBy:
type: string
version:
type: integer
type: object
domain.ReferralStats:
properties:
completedReferrals:
type: integer
pendingRewards:
type: number
totalReferrals:
type: integer
totalRewardEarned:
type: number
type: object
domain.Role: domain.Role:
enum: enum:
- super_admin - super_admin
@ -331,6 +384,8 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
required:
- phone_number
type: object type: object
handlers.CheckPhoneEmailExistRes: handlers.CheckPhoneEmailExistRes:
properties: properties:
@ -354,10 +409,30 @@ definitions:
example: 1 example: 1
type: integer type: integer
type: object type: object
handlers.CreateAdminReq:
properties:
company_id:
example: 1
type: integer
email:
example: john.doe@example.com
type: string
first_name:
example: John
type: string
last_name:
example: Doe
type: string
password:
example: password123
type: string
phone_number:
example: "1234567890"
type: string
type: object
handlers.CreateBetOutcomeReq: handlers.CreateBetOutcomeReq:
properties: properties:
event_id: event_id:
description: BetID int64 `json:"bet_id" example:"1"`
example: 1 example: 1
type: integer type: integer
market_id: market_id:
@ -375,9 +450,6 @@ definitions:
full_name: full_name:
example: John example: John
type: string type: string
is_shop_bet:
example: false
type: boolean
outcomes: outcomes:
items: items:
$ref: '#/definitions/handlers.CreateBetOutcomeReq' $ref: '#/definitions/handlers.CreateBetOutcomeReq'
@ -444,6 +516,9 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
suspended:
example: false
type: boolean
type: object type: object
handlers.CreateCompanyReq: handlers.CreateCompanyReq:
properties: properties:
@ -456,6 +531,9 @@ definitions:
type: object type: object
handlers.CreateManagerReq: handlers.CreateManagerReq:
properties: properties:
company_id:
example: 1
type: integer
email: email:
example: john.doe@example.com example: john.doe@example.com
type: string type: string
@ -611,7 +689,6 @@ definitions:
example: Doe example: Doe
type: string type: string
otp: otp:
description: Role string
example: "123456" example: "123456"
type: string type: string
password: password:
@ -636,13 +713,21 @@ definitions:
handlers.ResetPasswordReq: handlers.ResetPasswordReq:
properties: properties:
email: email:
example: john.doe@example.com
type: string type: string
otp: otp:
example: "123456"
type: string type: string
password: password:
example: newpassword123
minLength: 8
type: string type: string
phoneNumber: phone_number:
example: "1234567890"
type: string type: string
required:
- otp
- password
type: object type: object
handlers.SearchUserByNameOrPhoneReq: handlers.SearchUserByNameOrPhoneReq:
properties: properties:
@ -705,6 +790,9 @@ definitions:
id: id:
example: 1 example: 1
type: integer type: integer
number_of_outcomes:
example: 1
type: integer
payment_option: payment_option:
allOf: allOf:
- $ref: '#/definitions/domain.PaymentOption' - $ref: '#/definitions/domain.PaymentOption'
@ -762,12 +850,18 @@ definitions:
handlers.UpdateTransactionVerifiedReq: handlers.UpdateTransactionVerifiedReq:
properties: properties:
verified: verified:
example: true
type: boolean type: boolean
required:
- verified
type: object type: object
handlers.UpdateWalletActiveReq: handlers.UpdateWalletActiveReq:
properties: properties:
isActive: is_active:
example: true
type: boolean type: boolean
required:
- is_active
type: object type: object
handlers.UserProfileRes: handlers.UserProfileRes:
properties: properties:
@ -781,6 +875,8 @@ definitions:
type: string type: string
id: id:
type: integer type: integer
last_login:
type: string
last_name: last_name:
type: string type: string
phone_number: phone_number:
@ -824,6 +920,30 @@ definitions:
example: 1 example: 1
type: integer type: integer
type: object type: object
handlers.launchVirtualGameReq:
properties:
currency:
example: USD
type: string
game_id:
example: crash_001
type: string
mode:
enum:
- REAL
- DEMO
example: REAL
type: string
required:
- currency
- game_id
- mode
type: object
handlers.launchVirtualGameRes:
properties:
launch_url:
type: string
type: object
handlers.loginCustomerReq: handlers.loginCustomerReq:
properties: properties:
email: email:
@ -835,6 +955,8 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
required:
- password
type: object type: object
handlers.loginCustomerRes: handlers.loginCustomerRes:
properties: properties:
@ -848,14 +970,22 @@ definitions:
handlers.logoutReq: handlers.logoutReq:
properties: properties:
refresh_token: refresh_token:
example: <refresh-token>
type: string type: string
required:
- refresh_token
type: object type: object
handlers.refreshToken: handlers.refreshToken:
properties: properties:
access_token: access_token:
example: <jwt-token>
type: string type: string
refresh_token: refresh_token:
example: <refresh-token>
type: string type: string
required:
- access_token
- refresh_token
type: object type: object
handlers.updateUserReq: handlers.updateUserReq:
properties: properties:
@ -905,6 +1035,75 @@ info:
title: FortuneBet API title: FortuneBet API
version: "1.0" version: "1.0"
paths: paths:
/admin:
get:
consumes:
- application/json
description: Get all Admins
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get all Admins
tags:
- admin
post:
consumes:
- application/json
description: Create Admin
parameters:
- description: Create admin
in: body
name: manger
required: true
schema:
$ref: '#/definitions/handlers.CreateAdminReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Create Admin
tags:
- admin
/auth/login: /auth/login:
post: post:
consumes: consumes:
@ -1516,6 +1715,11 @@ paths:
- application/json - application/json
description: Update cashier description: Update cashier
parameters: parameters:
- description: Cashier ID
in: path
name: id
required: true
type: integer
- description: Update cashier - description: Update cashier
in: body in: body
name: cashier name: cashier
@ -1790,7 +1994,7 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Create Managers description: Create Manager
parameters: parameters:
- description: Create manager - description: Create manager
in: body in: body
@ -1817,7 +2021,7 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.APIResponse' $ref: '#/definitions/response.APIResponse'
summary: Create Managers summary: Create Manager
tags: tags:
- manager - manager
/managers/{id}: /managers/{id}:
@ -2079,6 +2283,95 @@ paths:
summary: Retrieve raw odds by Market ID summary: Retrieve raw odds by Market ID
tags: tags:
- prematch - prematch
/referral/settings:
get:
consumes:
- application/json
description: Retrieves current referral settings (admin only)
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.ReferralSettings'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"403":
description: Forbidden
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- Bearer: []
summary: Get referral settings
tags:
- referral
put:
consumes:
- application/json
description: Updates referral settings (admin only)
parameters:
- description: Referral settings
in: body
name: settings
required: true
schema:
$ref: '#/definitions/domain.ReferralSettings'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"403":
description: Forbidden
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- Bearer: []
summary: Update referral settings
tags:
- referral
/referral/stats:
get:
consumes:
- application/json
description: Retrieves referral statistics for the authenticated user
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.ReferralStats'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- Bearer: []
summary: Get referral statistics
tags:
- referral
/search/branch: /search/branch:
get: get:
consumes: consumes:
@ -2110,6 +2403,31 @@ paths:
summary: Search branches summary: Search branches
tags: tags:
- branch - branch
/search/company:
get:
consumes:
- application/json
description: Gets all companies
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/handlers.CompanyRes'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Gets all companies
tags:
- company
/supportedOperation: /supportedOperation:
get: get:
consumes: consumes:
@ -2333,7 +2651,7 @@ paths:
patch: patch:
consumes: consumes:
- application/json - application/json
description: Updates the cashed out field description: Updates the verified status of a transaction
parameters: parameters:
- description: Transaction ID - description: Transaction ID
in: path in: path
@ -2342,7 +2660,7 @@ paths:
type: integer type: integer
- description: Updates Transaction Verification - description: Updates Transaction Verification
in: body in: body
name: updateCashOut name: updateVerified
required: true required: true
schema: schema:
$ref: '#/definitions/handlers.UpdateTransactionVerifiedReq' $ref: '#/definitions/handlers.UpdateTransactionVerifiedReq'
@ -2361,7 +2679,7 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.APIResponse' $ref: '#/definitions/response.APIResponse'
summary: Updates the cashed out field summary: Updates the verified field of a transaction
tags: tags:
- transaction - transaction
/transfer/refill/:id: /transfer/refill/:id:
@ -2659,6 +2977,39 @@ paths:
summary: Send reset code summary: Send reset code
tags: tags:
- user - user
/user/single/{id}:
get:
consumes:
- application/json
description: Get a single user by id
parameters:
- description: User ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get user by id
tags:
- user
/user/wallet: /user/wallet:
get: get:
consumes: consumes:
@ -2690,6 +3041,72 @@ paths:
summary: Get customer wallet summary: Get customer wallet
tags: tags:
- wallet - wallet
/virtual-game/callback:
post:
consumes:
- application/json
description: Processes callbacks from PopOK for game events
parameters:
- description: Callback data
in: body
name: callback
required: true
schema:
$ref: '#/definitions/domain.PopOKCallback'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Handle PopOK game callback
tags:
- virtual-game
/virtual-game/launch:
post:
consumes:
- application/json
description: Generates a URL to launch a PopOK game
parameters:
- description: Game launch details
in: body
name: launch
required: true
schema:
$ref: '#/definitions/handlers.launchVirtualGameReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handlers.launchVirtualGameRes'
"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'
security:
- Bearer: []
summary: Launch a PopOK virtual game
tags:
- virtual-game
/wallet: /wallet:
get: get:
consumes: consumes:

View File

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

View File

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

View File

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

View File

@ -310,23 +310,24 @@ type TicketWithOutcome struct {
} }
type Transaction struct { type Transaction struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
CashierID int64 `json:"cashier_id"` CashierID int64 `json:"cashier_id"`
BetID int64 `json:"bet_id"` BetID int64 `json:"bet_id"`
Type int64 `json:"type"` NumberOfOutcomes int64 `json:"number_of_outcomes"`
PaymentOption int64 `json:"payment_option"` Type int64 `json:"type"`
FullName string `json:"full_name"` PaymentOption int64 `json:"payment_option"`
PhoneNumber string `json:"phone_number"` FullName string `json:"full_name"`
BankCode string `json:"bank_code"` PhoneNumber string `json:"phone_number"`
BeneficiaryName string `json:"beneficiary_name"` BankCode string `json:"bank_code"`
AccountName string `json:"account_name"` BeneficiaryName string `json:"beneficiary_name"`
AccountNumber string `json:"account_number"` AccountName string `json:"account_name"`
ReferenceNumber string `json:"reference_number"` AccountNumber string `json:"account_number"`
Verified bool `json:"verified"` ReferenceNumber string `json:"reference_number"`
CreatedAt pgtype.Timestamp `json:"created_at"` Verified bool `json:"verified"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type User struct { type User struct {
@ -341,6 +342,7 @@ type User struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
CompanyID pgtype.Int8 `json:"company_id"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"` SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
ReferralCode pgtype.Text `json:"referral_code"` ReferralCode pgtype.Text `json:"referral_code"`

View File

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

View File

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

5
go.mod
View File

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

6
go.sum
View File

@ -24,6 +24,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -49,6 +51,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/gofiber/contrib/websocket v1.3.4 h1:tWeBdbJ8q0WFQXariLN4dBIbGH9KBU75s0s7YXplOSg=
github.com/gofiber/contrib/websocket v1.3.4/go.mod h1:kTFBPC6YENCnKfKx0BoOFjgXxdz7E85/STdkmZPEmPs=
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
@ -116,6 +120,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
) )
func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams { func convertCreateCompany(company domain.CreateCompany) dbgen.CreateCompanyParams {
@ -46,6 +47,23 @@ func (s *Store) GetAllCompanies(ctx context.Context) ([]domain.Company, error) {
return companies, nil return companies, nil
} }
func (s *Store) SearchCompanyByName(ctx context.Context, name string) ([]domain.Company, error) {
dbCompanies, err := s.queries.SearchCompanyByName(ctx, pgtype.Text{
String: name,
Valid: true,
})
if err != nil {
return nil, err
}
var companies []domain.Company = make([]domain.Company, 0, len(dbCompanies))
for _, dbCompany := range dbCompanies {
companies = append(companies, convertDBCompany(dbCompany))
}
return companies, nil
}
func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) { func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.Company, error) {
dbCompany, err := s.queries.GetCompanyByID(ctx, id) dbCompany, err := s.queries.GetCompanyByID(ctx, id)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,6 +27,10 @@ func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.Company,
return s.companyStore.GetCompanyByID(ctx, id) return s.companyStore.GetCompanyByID(ctx, id)
} }
func (s *Service) SearchCompanyByName(ctx context.Context, name string) ([]domain.Company, error) {
return s.companyStore.SearchCompanyByName(ctx, name)
}
func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) { func (s *Service) UpdateCompany(ctx context.Context, id int64, company domain.UpdateCompany) (domain.Company, error) {
return s.companyStore.UpdateCompany(ctx, id, company) return s.companyStore.UpdateCompany(ctx, id, company)
} }

View File

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

View File

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

View File

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

View File

@ -6,11 +6,11 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"

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

View File

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

View File

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

View File

@ -143,6 +143,37 @@ func (h *Handler) GetCompanyByID(c *fiber.Ctx) error {
} }
// 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 []CompanyRes = make([]CompanyRes, 0, len(companies))
for _, company := range companies {
result = append(result, convertCompany(company))
}
return response.WriteJSON(c, fiber.StatusOK, "All Companies retrieved", result, nil)
}
// UpdateCompany godoc // UpdateCompany godoc
// @Summary Updates a company // @Summary Updates a company
// @Description Updates a company // @Description Updates a company

View File

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

View File

@ -3,7 +3,6 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -77,10 +76,10 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
} }
// Checking to make sure the event hasn't already started // Checking to make sure the event hasn't already started
currentTime := time.Now() // currentTime := time.Now()
if event.StartTime.Before(currentTime) { // if event.StartTime.Before(currentTime) {
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil) // return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
} // }
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr) odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
@ -184,8 +183,8 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id) ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
if err != nil { if err != nil {
h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err) // h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve ticket") return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
} }
res := TicketRes{ res := TicketRes{

View File

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

View File

@ -2,14 +2,25 @@ package handlers
import ( import (
"errors" "errors"
"strconv"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type CheckPhoneEmailExistReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
// CheckPhoneEmailExist godoc // CheckPhoneEmailExist godoc
// @Summary Check if phone number or email exist // @Summary Check if phone number or email exist
// @Description Check if phone number or email exist // @Description Check if phone number or email exist
@ -22,14 +33,6 @@ import (
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/checkPhoneEmailExist [post] // @Router /user/checkPhoneEmailExist [post]
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
type CheckPhoneEmailExistReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
var req CheckPhoneEmailExistReq var req CheckPhoneEmailExistReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -54,6 +57,11 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil)
} }
type RegisterCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
// SendRegisterCode godoc // SendRegisterCode godoc
// @Summary Send register code // @Summary Send register code
// @Description Send register code // @Description Send register code
@ -66,10 +74,6 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post] // @Router /user/sendRegisterCode [post]
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
type RegisterCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req RegisterCodeReq var req RegisterCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -101,6 +105,16 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
} }
type RegisterUserReq struct {
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"`
}
// RegisterUser godoc // RegisterUser godoc
// @Summary Register user // @Summary Register user
// @Description Register user // @Description Register user
@ -113,15 +127,6 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/register [post] // @Router /user/register [post]
func (h *Handler) RegisterUser(c *fiber.Ctx) error { func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
}
var req RegisterUserReq var req RegisterUserReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -192,6 +197,11 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
} }
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"`
}
// SendResetCode godoc // SendResetCode godoc
// @Summary Send reset code // @Summary Send reset code
// @Description Send reset code // @Description Send reset code
@ -204,10 +214,6 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post] // @Router /user/sendResetCode [post]
func (h *Handler) SendResetCode(c *fiber.Ctx) error { func (h *Handler) SendResetCode(c *fiber.Ctx) error {
type ResetCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req ResetCodeReq var req ResetCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -239,6 +245,13 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
} }
type ResetPasswordReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
Otp string `json:"otp" validate:"required" example:"123456"`
}
// ResetPassword godoc // ResetPassword godoc
// @Summary Reset password // @Summary Reset password
// @Description Reset password // @Description Reset password
@ -251,12 +264,6 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post] // @Router /user/resetPassword [post]
func (h *Handler) ResetPassword(c *fiber.Ctx) error { func (h *Handler) ResetPassword(c *fiber.Ctx) error {
type ResetPasswordReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
Otp string `json:"otp" validate:"required" example:"123456"`
}
var req ResetPasswordReq var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -301,6 +308,7 @@ type UserProfileRes struct {
PhoneVerified bool `json:"phone_verified"` PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
LastLogin time.Time `json:"last_login"`
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
} }
@ -330,6 +338,15 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile")
} }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
lastLogin = &user.CreatedAt
}
res := UserProfileRes{ res := UserProfileRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
@ -343,6 +360,7 @@ func (h *Handler) UserProfile(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt, SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin,
} }
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
} }
@ -374,6 +392,7 @@ type SearchUserByNameOrPhoneReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /user/search [post] // @Router /user/search [post]
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// TODO: Add filtering by role based on which user is calling this
var req SearchUserByNameOrPhoneReq var req SearchUserByNameOrPhoneReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("SearchUserByNameOrPhone failed", "error", err) h.logger.Error("SearchUserByNameOrPhone failed", "error", err)
@ -395,6 +414,15 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
} }
var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) var res []UserProfileRes = make([]UserProfileRes, 0, len(users))
for _, user := range users { for _, user := range users {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
lastLogin = &user.CreatedAt
}
res = append(res, UserProfileRes{ res = append(res, UserProfileRes{
ID: user.ID, ID: user.ID,
FirstName: user.FirstName, FirstName: user.FirstName,
@ -408,8 +436,80 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
UpdatedAt: user.UpdatedAt, UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt, SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin,
}) })
} }
return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil)
} }
// GetUserByID godoc
// @Summary Get user by id
// @Description Get a single user by id
// @Tags user
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/single/{id} [get]
func (h *Handler) GetUserByID(c *fiber.Ctx) error {
// branchId := int64(12) //c.Locals("branch_id").(int64)
// filter := user.Filter{
// Role: string(domain.RoleUser),
// BranchId: user.ValidBranchId{
// Value: branchId,
// Valid: true,
// },
// Page: c.QueryInt("page", 1),
// PageSize: c.QueryInt("page_size", 10),
// }
// valErrs, ok := validator.Validate(c, filter)
// if !ok {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
// }
userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil {
h.logger.Error("UpdateCashier failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("GetAllCashiers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
}
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
}
lastLogin = &user.CreatedAt
}
res := UserProfileRes{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
PhoneNumber: user.PhoneNumber,
Role: user.Role,
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
}
return response.WriteJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", res, nil)
}

View File

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

View File

@ -175,6 +175,10 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
} }
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
// UpdateWalletActive godoc // UpdateWalletActive godoc
// @Summary Activate and Deactivate Wallet // @Summary Activate and Deactivate Wallet
// @Description Can activate and deactivate wallet // @Description Can activate and deactivate wallet
@ -188,9 +192,6 @@ func (h *Handler) GetAllBranchWallets(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [patch] // @Router /wallet/{id} [patch]
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error { func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
walletID := c.Params("id") walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64) id, err := strconv.ParseInt(walletID, 10, 64)

View File

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

View File

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

View File

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