user and otp schema modification, SMTP setup using resend, afro SMS changed to direct API integration instead of using afoSMS library, most authentications implemented using username instead of email or phone number
This commit is contained in:
parent
47d70b029f
commit
915185c317
|
|
@ -48,7 +48,7 @@ INSERT INTO users (
|
|||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
password,
|
||||
|
|
@ -128,7 +128,7 @@ VALUES
|
|||
ON CONFLICT (id) DO UPDATE
|
||||
SET first_name = EXCLUDED.first_name,
|
||||
last_name = EXCLUDED.last_name,
|
||||
nick_name = EXCLUDED.nick_name,
|
||||
user_name = EXCLUDED.user_name,
|
||||
email = EXCLUDED.email,
|
||||
phone_number = EXCLUDED.phone_number,
|
||||
password = EXCLUDED.password,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ SELECT setval(
|
|||
)
|
||||
FROM users;
|
||||
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('organizations', 'id'),
|
||||
COALESCE(MAX(id), 1)
|
||||
)
|
||||
FROM organizations;
|
||||
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('courses', 'id'),
|
||||
|
|
|
|||
|
|
@ -33,16 +33,6 @@ DROP TABLE IF EXISTS lessons;
|
|||
DROP TABLE IF EXISTS course_modules;
|
||||
DROP TABLE IF EXISTS courses;
|
||||
|
||||
-- =========================================
|
||||
-- Organization Settings
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS organization_settings;
|
||||
DROP TABLE IF EXISTS global_settings;
|
||||
|
||||
-- =========================================
|
||||
-- Organizations (Tenants)
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS organizations;
|
||||
|
||||
-- =========================================
|
||||
-- Authentication & Security
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
id BIGSERIAL PRIMARY KEY,
|
||||
first_name VARCHAR(255) NOT NULL,
|
||||
last_name VARCHAR(255) NOT NULL,
|
||||
nick_name VARCHAR(100),
|
||||
user_name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE,
|
||||
phone_number VARCHAR(20) UNIQUE,
|
||||
role VARCHAR(50) NOT NULL, -- SUPER_ADMIN, ORG_ADMIN, INSTRUCTOR, STUDENT, SUPPORT
|
||||
|
||||
role VARCHAR(50) NOT NULL, -- SUPER_ADMIN, INSTRUCTOR, STUDENT, SUPPORT
|
||||
password BYTEA NOT NULL,
|
||||
age INT,
|
||||
education_level VARCHAR(100),
|
||||
|
|
@ -13,16 +14,19 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
region VARCHAR(100),
|
||||
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
suspended_at TIMESTAMPTZ,
|
||||
organization_id BIGINT,
|
||||
status VARCHAR(50) NOT NULL, -- PENDING, ACTIVE, SUSPENDED, DEACTIVATED
|
||||
last_login TIMESTAMPTZ,
|
||||
profile_completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
profile_picture_url TEXT,
|
||||
preferred_language VARCHAR(50),
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ,
|
||||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL),
|
||||
UNIQUE (email, organization_id),
|
||||
UNIQUE (phone_number, organization_id)
|
||||
|
||||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE refresh_tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
|
@ -34,9 +38,10 @@ CREATE TABLE refresh_tokens (
|
|||
|
||||
CREATE TABLE otps (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_name VARCHAR(100) NOT NULL,
|
||||
sent_to VARCHAR(255) NOT NULL,
|
||||
medium VARCHAR(50) NOT NULL, -- email, sms
|
||||
otp_for VARCHAR(50) NOT NULL, -- login, reset_password, verify
|
||||
otp_for VARCHAR(50) NOT NULL, -- register, reset
|
||||
otp VARCHAR(10) NOT NULL,
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
used_at TIMESTAMPTZ,
|
||||
|
|
@ -44,19 +49,8 @@ CREATE TABLE otps (
|
|||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE organizations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
owner_id BIGINT NOT NULL REFERENCES users(id),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE courses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
organization_id BIGINT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
instructor_id BIGINT NOT NULL REFERENCES users(id),
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
|
|
@ -170,15 +164,6 @@ CREATE TABLE global_settings (
|
|||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE organization_settings (
|
||||
organization_id BIGINT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (organization_id, key)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS reported_issues (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
-- name: UpdateExpiredOtp :exec
|
||||
UPDATE otps
|
||||
SET
|
||||
otp = $2,
|
||||
used = FALSE,
|
||||
used_at = NULL,
|
||||
expires_at = $3
|
||||
WHERE
|
||||
user_name = $1
|
||||
AND expires_at <= NOW();
|
||||
|
||||
-- name: CreateOtp :exec
|
||||
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, FALSE, $5, $6);
|
||||
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7);
|
||||
|
||||
-- name: GetOtp :one
|
||||
SELECT id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||
SELECT id, user_name, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||
FROM otps
|
||||
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
||||
WHERE user_name = $1
|
||||
ORDER BY created_at DESC LIMIT 1;
|
||||
|
||||
-- name: MarkOtpAsUsed :exec
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
-- name: IsUserPending :one
|
||||
SELECT
|
||||
CASE WHEN status = 'PENDING' THEN true ELSE false END AS is_pending
|
||||
FROM users
|
||||
WHERE user_name = $1
|
||||
LIMIT 1;
|
||||
|
||||
-- name: IsUserNameUnique :one
|
||||
SELECT
|
||||
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
||||
FROM users
|
||||
WHERE user_name = $1;
|
||||
|
||||
|
||||
-- name: CreateUser :one
|
||||
INSERT INTO users (
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -13,36 +27,35 @@ INSERT INTO users (
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id,
|
||||
created_at,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
$14,
|
||||
$15,
|
||||
$16,
|
||||
$17,
|
||||
$18
|
||||
$1, -- first_name
|
||||
$2, -- last_name
|
||||
$3, -- user_name
|
||||
$4, -- email
|
||||
$5, -- phone_number
|
||||
$6, -- role
|
||||
$7, -- password (BYTEA)
|
||||
$8, -- age
|
||||
$9, -- education_level
|
||||
$10, -- country
|
||||
$11, -- region
|
||||
$12, -- email_verified
|
||||
$13, -- phone_verified
|
||||
$14, -- status (PENDING | ACTIVE)
|
||||
$15, -- profile_completed
|
||||
$16, -- preferred_language
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
RETURNING id,
|
||||
RETURNING
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -52,22 +65,24 @@ RETURNING id,
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id;
|
||||
updated_at;
|
||||
|
||||
-- name: GetUserByID :one
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: GetAllUsers :many
|
||||
SELECT
|
||||
COUNT(*) OVER () AS total_count,
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -77,52 +92,44 @@ SELECT
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE (
|
||||
role = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
organization_id = $2
|
||||
OR $2 IS NULL
|
||||
role = $1 OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
first_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR last_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR email ILIKE '%' || sqlc.narg('query') || '%'
|
||||
OR sqlc.narg('query') IS NULL
|
||||
)
|
||||
AND (
|
||||
created_at > sqlc.narg('created_before')
|
||||
OR sqlc.narg('created_before') IS NULL
|
||||
created_at >= sqlc.narg('created_after')
|
||||
OR sqlc.narg('created_after') IS NULL
|
||||
)
|
||||
AND (
|
||||
created_at < sqlc.narg('created_after')
|
||||
OR sqlc.narg('created_after') IS NULL
|
||||
created_at <= sqlc.narg('created_before')
|
||||
OR sqlc.narg('created_before') IS NULL
|
||||
)
|
||||
LIMIT sqlc.narg('limit')
|
||||
OFFSET sqlc.narg('offset');
|
||||
|
||||
-- name: GetTotalUsers :one
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
wHERE (
|
||||
role = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
organization_id = $2
|
||||
OR $2 IS NULL
|
||||
);
|
||||
WHERE (role = $1 OR $1 IS NULL);
|
||||
|
||||
-- name: SearchUserByNameOrPhone :many
|
||||
SELECT id,
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -132,98 +139,113 @@ SELECT id,
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE (
|
||||
organization_id = sqlc.narg('organization_id')
|
||||
OR sqlc.narg('organization_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%'
|
||||
OR phone_number ILIKE '%' || $1 || '%'
|
||||
OR email ILIKE '%' || $1 || '%'
|
||||
)
|
||||
AND (
|
||||
role = sqlc.narg('role')
|
||||
OR sqlc.narg('role') IS NULL
|
||||
);
|
||||
|
||||
-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET first_name = $1,
|
||||
SET
|
||||
first_name = $1,
|
||||
last_name = $2,
|
||||
suspended = $3,
|
||||
status = $3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $4;
|
||||
-- name: UpdateUserOrganization :exec
|
||||
UPDATE users
|
||||
SET organization_id = $1
|
||||
WHERE id = $2;
|
||||
|
||||
-- name: DeleteUser :exec
|
||||
DELETE FROM users
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CheckPhoneEmailExist :one
|
||||
SELECT EXISTS (
|
||||
SELECT
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE users.phone_number = $1
|
||||
AND users.phone_number IS NOT NULL
|
||||
AND users.organization_id = $2
|
||||
FROM users u1
|
||||
WHERE u1.phone_number = $1
|
||||
) AS phone_exists,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE users.email = $3
|
||||
AND users.email IS NOT NULL
|
||||
AND users.organization_id = $2
|
||||
FROM users u2
|
||||
WHERE u2.email = $2
|
||||
) AS email_exists;
|
||||
-- name: GetUserByEmailPhone :one
|
||||
|
||||
-- name: GetUserByUserName :one
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
password, -- added this line
|
||||
password,
|
||||
age,
|
||||
education_level,
|
||||
country,
|
||||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
last_login,
|
||||
profile_picture_url,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE organization_id = $3
|
||||
AND (
|
||||
(email = $1 AND $1 IS NOT NULL)
|
||||
OR (phone_number = $2 AND $2 IS NOT NULL)
|
||||
)
|
||||
WHERE user_name = $1 AND $1 IS NOT NULL
|
||||
LIMIT 1;
|
||||
|
||||
-- name: GetUserByEmailPhone :one
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
password,
|
||||
age,
|
||||
education_level,
|
||||
country,
|
||||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
last_login,
|
||||
profile_picture_url,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE (email = $1 AND $1 IS NOT NULL)
|
||||
OR (phone_number = $2 AND $2 IS NOT NULL)
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePassword :exec
|
||||
UPDATE users
|
||||
SET password = $1,
|
||||
updated_at = $4
|
||||
WHERE (
|
||||
(email = $2 OR phone_number = $3)
|
||||
AND organization_id = $5
|
||||
);
|
||||
-- name: GetOwnerByOrganizationID :one
|
||||
SELECT users.*
|
||||
FROM organizations
|
||||
JOIN users ON organizations.owner_id = users.id
|
||||
WHERE organizations.id = $1;
|
||||
-- name: SuspendUser :exec
|
||||
UPDATE users
|
||||
SET suspended = $1,
|
||||
suspended_at = $2,
|
||||
SET
|
||||
password = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3;
|
||||
WHERE email = $2 OR phone_number = $3;
|
||||
|
||||
-- name: UpdateUserStatus :exec
|
||||
UPDATE users
|
||||
SET
|
||||
status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;
|
||||
|
|
|
|||
582
docs/docs.go
582
docs/docs.go
|
|
@ -393,58 +393,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/managers/{id}": {
|
||||
"put": {
|
||||
"description": "Update Managers",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"manager"
|
||||
],
|
||||
"summary": "Update Managers",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update Managers",
|
||||
"name": "Managers",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateManagerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/super-login": {
|
||||
"post": {
|
||||
"description": "Login super-admin",
|
||||
|
|
@ -761,7 +709,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -851,7 +799,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -875,9 +823,9 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/suspend": {
|
||||
"post": {
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"/api/v1/user/{user_name}/is-unique": {
|
||||
"get": {
|
||||
"description": "Returns whether the specified user_name is available (unique)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -887,35 +835,33 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Suspend or unsuspend a user",
|
||||
"summary": "Check if user_name is unique",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"name": "updateUserSuspend",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
||||
}
|
||||
"type": "string",
|
||||
"description": "User Name",
|
||||
"name": "user_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1171,7 +1117,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.RegisterUserReq"
|
||||
"$ref": "#/definitions/domain.RegisterUserReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -1243,52 +1189,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/search": {
|
||||
"post": {
|
||||
"description": "Search for user using name or phone",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Search for user using name or phone",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Search for using his name or phone",
|
||||
"name": "searchUserByNameOrPhone",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/sendRegisterCode": {
|
||||
"post": {
|
||||
"description": "Send register code",
|
||||
|
|
@ -1380,6 +1280,102 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/verify-otp": {
|
||||
"post": {
|
||||
"description": "Verify OTP for registration or other actions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Verify OTP",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Verify OTP",
|
||||
"name": "verifyOtp",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyOtpReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/{user_name}/is-pending": {
|
||||
"get": {
|
||||
"description": "Returns whether the specified user has a status of \"pending\"",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Check if user status is pending",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User Name",
|
||||
"name": "user_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
|
@ -1441,6 +1437,28 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OtpFor": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"reset",
|
||||
"register"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"OtpReset",
|
||||
"OtpRegister"
|
||||
]
|
||||
},
|
||||
"domain.OtpMedium": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"OtpMediumEmail",
|
||||
"OtpMediumSms"
|
||||
]
|
||||
},
|
||||
"domain.Pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1458,6 +1476,72 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"educationLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"organizationID": {
|
||||
"$ref": "#/definitions/domain.ValidInt64"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string"
|
||||
},
|
||||
"otpMedium": {
|
||||
"$ref": "#/definitions/domain.OtpMedium"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string"
|
||||
},
|
||||
"preferredLanguage": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"userName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -1475,6 +1559,135 @@ const docTemplate = `{
|
|||
"RoleSupport"
|
||||
]
|
||||
},
|
||||
"domain.UserProfileResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"education_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"preferred_language": {
|
||||
"type": "string"
|
||||
},
|
||||
"profile_completed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"profile_picture_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.UserStatus"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UserStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PENDING",
|
||||
"ACTIVE",
|
||||
"SUSPENDED",
|
||||
"DEACTIVATED"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UserStatusPending",
|
||||
"UserStatusActive",
|
||||
"UserStatusSuspended",
|
||||
"UserStatusDeactivated"
|
||||
]
|
||||
},
|
||||
"domain.ValidInt64": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyOtpReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"otp",
|
||||
"otp_for",
|
||||
"otp_medium"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Required if medium is email",
|
||||
"type": "string"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string"
|
||||
},
|
||||
"otp_for": {
|
||||
"$ref": "#/definitions/domain.OtpFor"
|
||||
},
|
||||
"otp_medium": {
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpMedium"
|
||||
}
|
||||
]
|
||||
},
|
||||
"phone_number": {
|
||||
"description": "Required if medium is SMS",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.AdminProfileRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1690,39 +1903,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string",
|
||||
"example": "123456"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"referral_code": {
|
||||
"type": "string",
|
||||
"example": "ABC123"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ResetCodeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1773,80 +1953,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 123
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UserProfileRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"referral_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"suspended_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.loginAdminReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -1870,20 +1976,17 @@ const docTemplate = `{
|
|||
"handlers.loginCustomerReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password"
|
||||
"password",
|
||||
"user_name"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"user_name": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
"example": "johndoe"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1951,27 +2054,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateManagerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -385,58 +385,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/managers/{id}": {
|
||||
"put": {
|
||||
"description": "Update Managers",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"manager"
|
||||
],
|
||||
"summary": "Update Managers",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update Managers",
|
||||
"name": "Managers",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.updateManagerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/super-login": {
|
||||
"post": {
|
||||
"description": "Login super-admin",
|
||||
|
|
@ -753,7 +701,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -843,7 +791,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -867,9 +815,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/user/suspend": {
|
||||
"post": {
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"/api/v1/user/{user_name}/is-unique": {
|
||||
"get": {
|
||||
"description": "Returns whether the specified user_name is available (unique)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -879,35 +827,33 @@
|
|||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Suspend or unsuspend a user",
|
||||
"summary": "Check if user_name is unique",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Suspend or unsuspend a user",
|
||||
"name": "updateUserSuspend",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
||||
}
|
||||
"type": "string",
|
||||
"description": "User Name",
|
||||
"name": "user_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1163,7 +1109,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.RegisterUserReq"
|
||||
"$ref": "#/definitions/domain.RegisterUserReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -1235,52 +1181,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/search": {
|
||||
"post": {
|
||||
"description": "Search for user using name or phone",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Search for user using name or phone",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Search for using his name or phone",
|
||||
"name": "searchUserByNameOrPhone",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/sendRegisterCode": {
|
||||
"post": {
|
||||
"description": "Send register code",
|
||||
|
|
@ -1372,6 +1272,102 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/verify-otp": {
|
||||
"post": {
|
||||
"description": "Verify OTP for registration or other actions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Verify OTP",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Verify OTP",
|
||||
"name": "verifyOtp",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyOtpReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/{tenant_slug}/user/{user_name}/is-pending": {
|
||||
"get": {
|
||||
"description": "Returns whether the specified user has a status of \"pending\"",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Check if user status is pending",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User Name",
|
||||
"name": "user_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
|
@ -1433,6 +1429,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OtpFor": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"reset",
|
||||
"register"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"OtpReset",
|
||||
"OtpRegister"
|
||||
]
|
||||
},
|
||||
"domain.OtpMedium": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"OtpMediumEmail",
|
||||
"OtpMediumSms"
|
||||
]
|
||||
},
|
||||
"domain.Pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1450,6 +1468,72 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"educationLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"organizationID": {
|
||||
"$ref": "#/definitions/domain.ValidInt64"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string"
|
||||
},
|
||||
"otpMedium": {
|
||||
"$ref": "#/definitions/domain.OtpMedium"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string"
|
||||
},
|
||||
"preferredLanguage": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"userName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -1467,6 +1551,135 @@
|
|||
"RoleSupport"
|
||||
]
|
||||
},
|
||||
"domain.UserProfileResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"education_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"preferred_language": {
|
||||
"type": "string"
|
||||
},
|
||||
"profile_completed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"profile_picture_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.UserStatus"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UserStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PENDING",
|
||||
"ACTIVE",
|
||||
"SUSPENDED",
|
||||
"DEACTIVATED"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UserStatusPending",
|
||||
"UserStatusActive",
|
||||
"UserStatusSuspended",
|
||||
"UserStatusDeactivated"
|
||||
]
|
||||
},
|
||||
"domain.ValidInt64": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyOtpReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"otp",
|
||||
"otp_for",
|
||||
"otp_medium"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Required if medium is email",
|
||||
"type": "string"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string"
|
||||
},
|
||||
"otp_for": {
|
||||
"$ref": "#/definitions/domain.OtpFor"
|
||||
},
|
||||
"otp_medium": {
|
||||
"enum": [
|
||||
"email",
|
||||
"sms"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpMedium"
|
||||
}
|
||||
]
|
||||
},
|
||||
"phone_number": {
|
||||
"description": "Required if medium is SMS",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.AdminProfileRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1682,39 +1895,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string",
|
||||
"example": "123456"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"referral_code": {
|
||||
"type": "string",
|
||||
"example": "ABC123"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.ResetCodeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1765,80 +1945,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 123
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateUserSuspendRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UserProfileRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"referral_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/domain.Role"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"suspended_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.loginAdminReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -1862,20 +1968,17 @@
|
|||
"handlers.loginCustomerReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password"
|
||||
"password",
|
||||
"user_name"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"user_name": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
"example": "johndoe"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1943,27 +2046,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.updateManagerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"example": "John"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"example": "Doe"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ definitions:
|
|||
pagination:
|
||||
$ref: '#/definitions/domain.Pagination'
|
||||
type: object
|
||||
domain.OtpFor:
|
||||
enum:
|
||||
- reset
|
||||
- register
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- OtpReset
|
||||
- OtpRegister
|
||||
domain.OtpMedium:
|
||||
enum:
|
||||
- email
|
||||
- sms
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- OtpMediumEmail
|
||||
- OtpMediumSms
|
||||
domain.Pagination:
|
||||
properties:
|
||||
current_page:
|
||||
|
|
@ -48,6 +64,50 @@ definitions:
|
|||
total_pages:
|
||||
type: integer
|
||||
type: object
|
||||
domain.RegisterUserReq:
|
||||
properties:
|
||||
age:
|
||||
type: integer
|
||||
country:
|
||||
type: string
|
||||
educationLevel:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
organizationID:
|
||||
$ref: '#/definitions/domain.ValidInt64'
|
||||
otp:
|
||||
type: string
|
||||
otpMedium:
|
||||
$ref: '#/definitions/domain.OtpMedium'
|
||||
password:
|
||||
type: string
|
||||
phoneNumber:
|
||||
type: string
|
||||
preferredLanguage:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
userName:
|
||||
type: string
|
||||
type: object
|
||||
domain.Response:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
metadata: {}
|
||||
status_code:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.Role:
|
||||
enum:
|
||||
- super_admin
|
||||
|
|
@ -62,6 +122,93 @@ definitions:
|
|||
- RoleStudent
|
||||
- RoleInstructor
|
||||
- RoleSupport
|
||||
domain.UserProfileResponse:
|
||||
properties:
|
||||
age:
|
||||
type: integer
|
||||
country:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
education_level:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
email_verified:
|
||||
type: boolean
|
||||
first_name:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
last_login:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
organization_id:
|
||||
type: integer
|
||||
phone_number:
|
||||
type: string
|
||||
phone_verified:
|
||||
type: boolean
|
||||
preferred_language:
|
||||
type: string
|
||||
profile_completed:
|
||||
type: boolean
|
||||
profile_picture_url:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
role:
|
||||
$ref: '#/definitions/domain.Role'
|
||||
status:
|
||||
$ref: '#/definitions/domain.UserStatus'
|
||||
updated_at:
|
||||
type: string
|
||||
user_name:
|
||||
type: string
|
||||
type: object
|
||||
domain.UserStatus:
|
||||
enum:
|
||||
- PENDING
|
||||
- ACTIVE
|
||||
- SUSPENDED
|
||||
- DEACTIVATED
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- UserStatusPending
|
||||
- UserStatusActive
|
||||
- UserStatusSuspended
|
||||
- UserStatusDeactivated
|
||||
domain.ValidInt64:
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
value:
|
||||
type: integer
|
||||
type: object
|
||||
domain.VerifyOtpReq:
|
||||
properties:
|
||||
email:
|
||||
description: Required if medium is email
|
||||
type: string
|
||||
otp:
|
||||
type: string
|
||||
otp_for:
|
||||
$ref: '#/definitions/domain.OtpFor'
|
||||
otp_medium:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OtpMedium'
|
||||
enum:
|
||||
- email
|
||||
- sms
|
||||
phone_number:
|
||||
description: Required if medium is SMS
|
||||
type: string
|
||||
required:
|
||||
- otp
|
||||
- otp_for
|
||||
- otp_medium
|
||||
type: object
|
||||
handlers.AdminProfileRes:
|
||||
properties:
|
||||
created_at:
|
||||
|
|
@ -206,30 +353,6 @@ definitions:
|
|||
example: "1234567890"
|
||||
type: string
|
||||
type: object
|
||||
handlers.RegisterUserReq:
|
||||
properties:
|
||||
email:
|
||||
example: john.doe@example.com
|
||||
type: string
|
||||
first_name:
|
||||
example: John
|
||||
type: string
|
||||
last_name:
|
||||
example: Doe
|
||||
type: string
|
||||
otp:
|
||||
example: "123456"
|
||||
type: string
|
||||
password:
|
||||
example: password123
|
||||
type: string
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
referral_code:
|
||||
example: ABC123
|
||||
type: string
|
||||
type: object
|
||||
handlers.ResetCodeReq:
|
||||
properties:
|
||||
email:
|
||||
|
|
@ -265,55 +388,6 @@ definitions:
|
|||
role:
|
||||
$ref: '#/definitions/domain.Role'
|
||||
type: object
|
||||
handlers.UpdateUserSuspendReq:
|
||||
properties:
|
||||
suspended:
|
||||
example: true
|
||||
type: boolean
|
||||
user_id:
|
||||
example: 123
|
||||
type: integer
|
||||
required:
|
||||
- user_id
|
||||
type: object
|
||||
handlers.UpdateUserSuspendRes:
|
||||
properties:
|
||||
suspended:
|
||||
type: boolean
|
||||
user_id:
|
||||
type: integer
|
||||
type: object
|
||||
handlers.UserProfileRes:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
email_verified:
|
||||
type: boolean
|
||||
first_name:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
last_login:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
phone_number:
|
||||
type: string
|
||||
phone_verified:
|
||||
type: boolean
|
||||
referral_code:
|
||||
type: string
|
||||
role:
|
||||
$ref: '#/definitions/domain.Role'
|
||||
suspended:
|
||||
type: boolean
|
||||
suspended_at:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
handlers.loginAdminReq:
|
||||
properties:
|
||||
email:
|
||||
|
|
@ -330,17 +404,15 @@ definitions:
|
|||
type: object
|
||||
handlers.loginCustomerReq:
|
||||
properties:
|
||||
email:
|
||||
example: john.doe@example.com
|
||||
type: string
|
||||
password:
|
||||
example: password123
|
||||
type: string
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
user_name:
|
||||
example: johndoe
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- user_name
|
||||
type: object
|
||||
handlers.loginCustomerRes:
|
||||
properties:
|
||||
|
|
@ -386,21 +458,6 @@ definitions:
|
|||
example: false
|
||||
type: boolean
|
||||
type: object
|
||||
handlers.updateManagerReq:
|
||||
properties:
|
||||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
first_name:
|
||||
example: John
|
||||
type: string
|
||||
last_name:
|
||||
example: Doe
|
||||
type: string
|
||||
suspended:
|
||||
example: false
|
||||
type: boolean
|
||||
type: object
|
||||
response.APIResponse:
|
||||
properties:
|
||||
data: {}
|
||||
|
|
@ -505,6 +562,39 @@ paths:
|
|||
summary: Login customer
|
||||
tags:
|
||||
- auth
|
||||
/api/v1/{tenant_slug}/user/{user_name}/is-pending:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns whether the specified user has a status of "pending"
|
||||
parameters:
|
||||
- description: User Name
|
||||
in: path
|
||||
name: user_name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Check if user status is pending
|
||||
tags:
|
||||
- user
|
||||
/api/v1/{tenant_slug}/user/admin-profile:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -596,7 +686,7 @@ paths:
|
|||
name: registerUser
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.RegisterUserReq'
|
||||
$ref: '#/definitions/domain.RegisterUserReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -645,36 +735,6 @@ paths:
|
|||
summary: Reset tenant password
|
||||
tags:
|
||||
- user
|
||||
/api/v1/{tenant_slug}/user/search:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search for user using name or phone
|
||||
parameters:
|
||||
- description: Search for using his name or phone
|
||||
in: body
|
||||
name: searchUserByNameOrPhone
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.SearchUserByNameOrPhoneReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Search for user using name or phone
|
||||
tags:
|
||||
- user
|
||||
/api/v1/{tenant_slug}/user/sendRegisterCode:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -735,6 +795,36 @@ paths:
|
|||
summary: Send reset code
|
||||
tags:
|
||||
- user
|
||||
/api/v1/{tenant_slug}/user/verify-otp:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Verify OTP for registration or other actions
|
||||
parameters:
|
||||
- description: Verify OTP
|
||||
in: body
|
||||
name: verifyOtp
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VerifyOtpReq'
|
||||
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: Verify OTP
|
||||
tags:
|
||||
- user
|
||||
/api/v1/admin:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -980,40 +1070,6 @@ paths:
|
|||
summary: Retrieve application logs with filtering and pagination
|
||||
tags:
|
||||
- Logs
|
||||
/api/v1/managers/{id}:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update Managers
|
||||
parameters:
|
||||
- description: Update Managers
|
||||
in: body
|
||||
name: Managers
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.updateManagerReq'
|
||||
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: Update Managers
|
||||
tags:
|
||||
- manager
|
||||
/api/v1/super-login:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -1144,6 +1200,35 @@ paths:
|
|||
summary: Check if phone number or email exist
|
||||
tags:
|
||||
- user
|
||||
/api/v1/user/{user_name}/is-unique:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns whether the specified user_name is available (unique)
|
||||
parameters:
|
||||
- description: User Name
|
||||
in: path
|
||||
name: user_name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Check if user_name is unique
|
||||
tags:
|
||||
- user
|
||||
/api/v1/user/delete/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
|
|
@ -1221,7 +1306,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
$ref: '#/definitions/domain.UserProfileResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1280,7 +1365,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
$ref: '#/definitions/domain.UserProfileResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1296,36 +1381,6 @@ paths:
|
|||
summary: Get user by id
|
||||
tags:
|
||||
- user
|
||||
/api/v1/user/suspend:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Suspend or unsuspend a user
|
||||
parameters:
|
||||
- description: Suspend or unsuspend a user
|
||||
in: body
|
||||
name: updateUserSuspend
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UpdateUserSuspendReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UpdateUserSuspendRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Suspend or unsuspend a user
|
||||
tags:
|
||||
- user
|
||||
securityDefinitions:
|
||||
Bearer:
|
||||
in: header
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ type AssessmentSubmission struct {
|
|||
|
||||
type Course struct {
|
||||
ID int64 `json:"id"`
|
||||
OrganizationID int64 `json:"organization_id"`
|
||||
InstructorID int64 `json:"instructor_id"`
|
||||
Title string `json:"title"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
|
|
@ -97,26 +96,9 @@ type Notification struct {
|
|||
ReadAt pgtype.Timestamptz `json:"read_at"`
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
OwnerID int64 `json:"owner_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type OrganizationSetting struct {
|
||||
OrganizationID int64 `json:"organization_id"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Otp struct {
|
||||
ID int64 `json:"id"`
|
||||
UserName string `json:"user_name"`
|
||||
SentTo string `json:"sent_to"`
|
||||
Medium string `json:"medium"`
|
||||
OtpFor string `json:"otp_for"`
|
||||
|
|
@ -127,19 +109,6 @@ type Otp struct {
|
|||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
type ReferralCode struct {
|
||||
ID int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
ReferrerID int64 `json:"referrer_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
MaxUses pgtype.Int4 `json:"max_uses"`
|
||||
CurrentUses int32 `json:"current_uses"`
|
||||
IncentiveType string `json:"incentive_type"`
|
||||
IncentiveValue pgtype.Text `json:"incentive_value"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type RefreshToken struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
|
|
@ -166,7 +135,7 @@ type User struct {
|
|||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -177,17 +146,11 @@ type User struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
Status string `json:"status"`
|
||||
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
ProfilePictureUrl pgtype.Text `json:"profile_picture_url"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserReferral struct {
|
||||
ID int64 `json:"id"`
|
||||
ReferrerID int64 `json:"referrer_id"`
|
||||
ReferredUserID int64 `json:"referred_user_id"`
|
||||
ReferralCodeID int64 `json:"referral_code_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import (
|
|||
)
|
||||
|
||||
const CreateOtp = `-- name: CreateOtp :exec
|
||||
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, FALSE, $5, $6)
|
||||
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7)
|
||||
`
|
||||
|
||||
type CreateOtpParams struct {
|
||||
UserName string `json:"user_name"`
|
||||
SentTo string `json:"sent_to"`
|
||||
Medium string `json:"medium"`
|
||||
OtpFor string `json:"otp_for"`
|
||||
|
|
@ -27,6 +28,7 @@ type CreateOtpParams struct {
|
|||
|
||||
func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
||||
_, err := q.db.Exec(ctx, CreateOtp,
|
||||
arg.UserName,
|
||||
arg.SentTo,
|
||||
arg.Medium,
|
||||
arg.OtpFor,
|
||||
|
|
@ -38,20 +40,15 @@ func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
|||
}
|
||||
|
||||
const GetOtp = `-- name: GetOtp :one
|
||||
SELECT id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||
SELECT id, user_name, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||
FROM otps
|
||||
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
||||
WHERE user_name = $1
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
`
|
||||
|
||||
type GetOtpParams struct {
|
||||
SentTo string `json:"sent_to"`
|
||||
OtpFor string `json:"otp_for"`
|
||||
Medium string `json:"medium"`
|
||||
}
|
||||
|
||||
type GetOtpRow struct {
|
||||
ID int64 `json:"id"`
|
||||
UserName string `json:"user_name"`
|
||||
SentTo string `json:"sent_to"`
|
||||
Medium string `json:"medium"`
|
||||
OtpFor string `json:"otp_for"`
|
||||
|
|
@ -62,11 +59,12 @@ type GetOtpRow struct {
|
|||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetOtp(ctx context.Context, arg GetOtpParams) (GetOtpRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetOtp, arg.SentTo, arg.OtpFor, arg.Medium)
|
||||
func (q *Queries) GetOtp(ctx context.Context, userName string) (GetOtpRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetOtp, userName)
|
||||
var i GetOtpRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserName,
|
||||
&i.SentTo,
|
||||
&i.Medium,
|
||||
&i.OtpFor,
|
||||
|
|
@ -94,3 +92,26 @@ func (q *Queries) MarkOtpAsUsed(ctx context.Context, arg MarkOtpAsUsedParams) er
|
|||
_, err := q.db.Exec(ctx, MarkOtpAsUsed, arg.ID, arg.UsedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateExpiredOtp = `-- name: UpdateExpiredOtp :exec
|
||||
UPDATE otps
|
||||
SET
|
||||
otp = $2,
|
||||
used = FALSE,
|
||||
used_at = NULL,
|
||||
expires_at = $3
|
||||
WHERE
|
||||
user_name = $1
|
||||
AND expires_at <= NOW()
|
||||
`
|
||||
|
||||
type UpdateExpiredOtpParams struct {
|
||||
UserName string `json:"user_name"`
|
||||
Otp string `json:"otp"`
|
||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateExpiredOtp(ctx context.Context, arg UpdateExpiredOtpParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateExpiredOtp, arg.UserName, arg.Otp, arg.ExpiresAt)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,21 @@ import (
|
|||
)
|
||||
|
||||
const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one
|
||||
SELECT EXISTS (
|
||||
SELECT
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE users.phone_number = $1
|
||||
AND users.phone_number IS NOT NULL
|
||||
AND users.organization_id = $2
|
||||
FROM users u1
|
||||
WHERE u1.phone_number = $1
|
||||
) AS phone_exists,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE users.email = $3
|
||||
AND users.email IS NOT NULL
|
||||
AND users.organization_id = $2
|
||||
FROM users u2
|
||||
WHERE u2.email = $2
|
||||
) AS email_exists
|
||||
`
|
||||
|
||||
type CheckPhoneEmailExistParams struct {
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +36,7 @@ type CheckPhoneEmailExistRow struct {
|
|||
}
|
||||
|
||||
func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailExistParams) (CheckPhoneEmailExistRow, error) {
|
||||
row := q.db.QueryRow(ctx, CheckPhoneEmailExist, arg.PhoneNumber, arg.OrganizationID, arg.Email)
|
||||
row := q.db.QueryRow(ctx, CheckPhoneEmailExist, arg.PhoneNumber, arg.Email)
|
||||
var i CheckPhoneEmailExistRow
|
||||
err := row.Scan(&i.PhoneExists, &i.EmailExists)
|
||||
return i, err
|
||||
|
|
@ -50,7 +46,7 @@ const CreateUser = `-- name: CreateUser :one
|
|||
INSERT INTO users (
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -61,36 +57,35 @@ INSERT INTO users (
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id,
|
||||
created_at,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
$14,
|
||||
$15,
|
||||
$16,
|
||||
$17,
|
||||
$18
|
||||
$1, -- first_name
|
||||
$2, -- last_name
|
||||
$3, -- user_name
|
||||
$4, -- email
|
||||
$5, -- phone_number
|
||||
$6, -- role
|
||||
$7, -- password (BYTEA)
|
||||
$8, -- age
|
||||
$9, -- education_level
|
||||
$10, -- country
|
||||
$11, -- region
|
||||
$12, -- email_verified
|
||||
$13, -- phone_verified
|
||||
$14, -- status (PENDING | ACTIVE)
|
||||
$15, -- profile_completed
|
||||
$16, -- preferred_language
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
RETURNING id,
|
||||
RETURNING
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -100,17 +95,17 @@ RETURNING id,
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
`
|
||||
|
||||
type CreateUserParams struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -121,18 +116,16 @@ type CreateUserParams struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
}
|
||||
|
||||
type CreateUserRow struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -142,18 +135,18 @@ type CreateUserRow struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
|
||||
row := q.db.QueryRow(ctx, CreateUser,
|
||||
arg.FirstName,
|
||||
arg.LastName,
|
||||
arg.NickName,
|
||||
arg.UserName,
|
||||
arg.Email,
|
||||
arg.PhoneNumber,
|
||||
arg.Role,
|
||||
|
|
@ -164,18 +157,16 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
|
|||
arg.Region,
|
||||
arg.EmailVerified,
|
||||
arg.PhoneVerified,
|
||||
arg.Suspended,
|
||||
arg.SuspendedAt,
|
||||
arg.OrganizationID,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
arg.Status,
|
||||
arg.ProfileCompleted,
|
||||
arg.PreferredLanguage,
|
||||
)
|
||||
var i CreateUserRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
|
|
@ -185,11 +176,11 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
|
|||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Status,
|
||||
&i.ProfileCompleted,
|
||||
&i.PreferredLanguage,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -210,7 +201,7 @@ SELECT
|
|||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -220,44 +211,39 @@ SELECT
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE (
|
||||
role = $1
|
||||
OR $1 IS NULL
|
||||
role = $1 OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
organization_id = $2
|
||||
first_name ILIKE '%' || $2 || '%'
|
||||
OR last_name ILIKE '%' || $2 || '%'
|
||||
OR phone_number ILIKE '%' || $2 || '%'
|
||||
OR email ILIKE '%' || $2 || '%'
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
first_name ILIKE '%' || $3 || '%'
|
||||
OR last_name ILIKE '%' || $3 || '%'
|
||||
OR phone_number ILIKE '%' || $3 || '%'
|
||||
created_at >= $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
created_at > $4
|
||||
created_at <= $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
AND (
|
||||
created_at < $5
|
||||
OR $5 IS NULL
|
||||
)
|
||||
LIMIT $7
|
||||
OFFSET $6
|
||||
LIMIT $6
|
||||
OFFSET $5
|
||||
`
|
||||
|
||||
type GetAllUsersParams struct {
|
||||
Role string `json:"role"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
Query pgtype.Text `json:"query"`
|
||||
CreatedBefore pgtype.Timestamptz `json:"created_before"`
|
||||
CreatedAfter pgtype.Timestamptz `json:"created_after"`
|
||||
CreatedBefore pgtype.Timestamptz `json:"created_before"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
|
@ -267,7 +253,7 @@ type GetAllUsersRow struct {
|
|||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -277,20 +263,19 @@ type GetAllUsersRow struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]GetAllUsersRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllUsers,
|
||||
arg.Role,
|
||||
arg.OrganizationID,
|
||||
arg.Query,
|
||||
arg.CreatedBefore,
|
||||
arg.CreatedAfter,
|
||||
arg.CreatedBefore,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
|
|
@ -306,7 +291,7 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
|||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
|
|
@ -316,11 +301,11 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
|||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Status,
|
||||
&i.ProfileCompleted,
|
||||
&i.PreferredLanguage,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -332,60 +317,14 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetOwnerByOrganizationID = `-- name: GetOwnerByOrganizationID :one
|
||||
SELECT users.id, users.first_name, users.last_name, users.nick_name, users.email, users.phone_number, users.role, users.password, users.age, users.education_level, users.country, users.region, users.email_verified, users.phone_verified, users.suspended, users.suspended_at, users.organization_id, users.created_at, users.updated_at
|
||||
FROM organizations
|
||||
JOIN users ON organizations.owner_id = users.id
|
||||
WHERE organizations.id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetOwnerByOrganizationID(ctx context.Context, id int64) (User, error) {
|
||||
row := q.db.QueryRow(ctx, GetOwnerByOrganizationID, id)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
&i.Password,
|
||||
&i.Age,
|
||||
&i.EducationLevel,
|
||||
&i.Country,
|
||||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetTotalUsers = `-- name: GetTotalUsers :one
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
wHERE (
|
||||
role = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
organization_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
WHERE (role = $1 OR $1 IS NULL)
|
||||
`
|
||||
|
||||
type GetTotalUsersParams struct {
|
||||
Role string `json:"role"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalUsers(ctx context.Context, arg GetTotalUsersParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalUsers, arg.Role, arg.OrganizationID)
|
||||
func (q *Queries) GetTotalUsers(ctx context.Context, role string) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalUsers, role)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
|
|
@ -396,42 +335,40 @@ SELECT
|
|||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
password, -- added this line
|
||||
password,
|
||||
age,
|
||||
education_level,
|
||||
country,
|
||||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
last_login,
|
||||
profile_picture_url,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE organization_id = $3
|
||||
AND (
|
||||
(email = $1 AND $1 IS NOT NULL)
|
||||
WHERE (email = $1 AND $1 IS NOT NULL)
|
||||
OR (phone_number = $2 AND $2 IS NOT NULL)
|
||||
)
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetUserByEmailPhoneParams struct {
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
type GetUserByEmailPhoneRow struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -442,21 +379,23 @@ type GetUserByEmailPhoneRow struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||
ProfilePictureUrl pgtype.Text `json:"profile_picture_url"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (GetUserByEmailPhoneRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber, arg.OrganizationID)
|
||||
row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber)
|
||||
var i GetUserByEmailPhoneRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
|
|
@ -467,17 +406,19 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
|
|||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Status,
|
||||
&i.ProfileCompleted,
|
||||
&i.LastLogin,
|
||||
&i.ProfilePictureUrl,
|
||||
&i.PreferredLanguage,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetUserByID = `-- name: GetUserByID :one
|
||||
SELECT id, first_name, last_name, nick_name, email, phone_number, role, password, age, education_level, country, region, email_verified, phone_verified, suspended, suspended_at, organization_id, created_at, updated_at
|
||||
SELECT id, first_name, last_name, user_name, email, phone_number, role, password, age, education_level, country, region, email_verified, phone_verified, status, last_login, profile_completed, profile_picture_url, preferred_language, created_at, updated_at
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -489,7 +430,7 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
|||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
|
|
@ -500,20 +441,133 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
|||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
&i.Status,
|
||||
&i.LastLogin,
|
||||
&i.ProfileCompleted,
|
||||
&i.ProfilePictureUrl,
|
||||
&i.PreferredLanguage,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many
|
||||
SELECT id,
|
||||
const GetUserByUserName = `-- name: GetUserByUserName :one
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
nick_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
password,
|
||||
age,
|
||||
education_level,
|
||||
country,
|
||||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
last_login,
|
||||
profile_picture_url,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE user_name = $1 AND $1 IS NOT NULL
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetUserByUserNameRow struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
Password []byte `json:"password"`
|
||||
Age pgtype.Int4 `json:"age"`
|
||||
EducationLevel pgtype.Text `json:"education_level"`
|
||||
Country pgtype.Text `json:"country"`
|
||||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||
ProfilePictureUrl pgtype.Text `json:"profile_picture_url"`
|
||||
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByUserName(ctx context.Context, userName string) (GetUserByUserNameRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetUserByUserName, userName)
|
||||
var i GetUserByUserNameRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
&i.Password,
|
||||
&i.Age,
|
||||
&i.EducationLevel,
|
||||
&i.Country,
|
||||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Status,
|
||||
&i.ProfileCompleted,
|
||||
&i.LastLogin,
|
||||
&i.ProfilePictureUrl,
|
||||
&i.PreferredLanguage,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const IsUserNameUnique = `-- name: IsUserNameUnique :one
|
||||
SELECT
|
||||
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
||||
FROM users
|
||||
WHERE user_name = $1
|
||||
`
|
||||
|
||||
func (q *Queries) IsUserNameUnique(ctx context.Context, userName string) (bool, error) {
|
||||
row := q.db.QueryRow(ctx, IsUserNameUnique, userName)
|
||||
var is_unique bool
|
||||
err := row.Scan(&is_unique)
|
||||
return is_unique, err
|
||||
}
|
||||
|
||||
const IsUserPending = `-- name: IsUserPending :one
|
||||
SELECT
|
||||
CASE WHEN status = 'PENDING' THEN true ELSE false END AS is_pending
|
||||
FROM users
|
||||
WHERE user_name = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) IsUserPending(ctx context.Context, userName string) (bool, error) {
|
||||
row := q.db.QueryRow(ctx, IsUserPending, userName)
|
||||
var is_pending bool
|
||||
err := row.Scan(&is_pending)
|
||||
return is_pending, err
|
||||
}
|
||||
|
||||
const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
role,
|
||||
|
|
@ -523,30 +577,25 @@ SELECT id,
|
|||
region,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
status,
|
||||
profile_completed,
|
||||
created_at,
|
||||
updated_at,
|
||||
suspended,
|
||||
suspended_at,
|
||||
organization_id
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE (
|
||||
organization_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
first_name ILIKE '%' || $1 || '%'
|
||||
OR last_name ILIKE '%' || $1 || '%'
|
||||
OR phone_number LIKE '%' || $1 || '%'
|
||||
OR phone_number ILIKE '%' || $1 || '%'
|
||||
OR email ILIKE '%' || $1 || '%'
|
||||
)
|
||||
AND (
|
||||
role = $3
|
||||
OR $3 IS NULL
|
||||
role = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type SearchUserByNameOrPhoneParams struct {
|
||||
Column1 pgtype.Text `json:"column_1"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
Role pgtype.Text `json:"role"`
|
||||
}
|
||||
|
||||
|
|
@ -554,7 +603,7 @@ type SearchUserByNameOrPhoneRow struct {
|
|||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName pgtype.Text `json:"nick_name"`
|
||||
UserName string `json:"user_name"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -564,15 +613,14 @@ type SearchUserByNameOrPhoneRow struct {
|
|||
Region pgtype.Text `json:"region"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status string `json:"status"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByNameOrPhoneParams) ([]SearchUserByNameOrPhoneRow, error) {
|
||||
rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, arg.Column1, arg.OrganizationID, arg.Role)
|
||||
rows, err := q.db.Query(ctx, SearchUserByNameOrPhone, arg.Column1, arg.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -584,7 +632,7 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
|||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.NickName,
|
||||
&i.UserName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Role,
|
||||
|
|
@ -594,11 +642,10 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
|||
&i.Region,
|
||||
&i.EmailVerified,
|
||||
&i.PhoneVerified,
|
||||
&i.Status,
|
||||
&i.ProfileCompleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Suspended,
|
||||
&i.SuspendedAt,
|
||||
&i.OrganizationID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -610,59 +657,31 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const SuspendUser = `-- name: SuspendUser :exec
|
||||
UPDATE users
|
||||
SET suspended = $1,
|
||||
suspended_at = $2,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3
|
||||
`
|
||||
|
||||
type SuspendUserParams struct {
|
||||
Suspended bool `json:"suspended"`
|
||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) SuspendUser(ctx context.Context, arg SuspendUserParams) error {
|
||||
_, err := q.db.Exec(ctx, SuspendUser, arg.Suspended, arg.SuspendedAt, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdatePassword = `-- name: UpdatePassword :exec
|
||||
UPDATE users
|
||||
SET password = $1,
|
||||
updated_at = $4
|
||||
WHERE (
|
||||
(email = $2 OR phone_number = $3)
|
||||
AND organization_id = $5
|
||||
)
|
||||
SET
|
||||
password = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE email = $2 OR phone_number = $3
|
||||
`
|
||||
|
||||
type UpdatePasswordParams struct {
|
||||
Password []byte `json:"password"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdatePassword,
|
||||
arg.Password,
|
||||
arg.Email,
|
||||
arg.PhoneNumber,
|
||||
arg.UpdatedAt,
|
||||
arg.OrganizationID,
|
||||
)
|
||||
_, err := q.db.Exec(ctx, UpdatePassword, arg.Password, arg.Email, arg.PhoneNumber)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateUser = `-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET first_name = $1,
|
||||
SET
|
||||
first_name = $1,
|
||||
last_name = $2,
|
||||
suspended = $3,
|
||||
status = $3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $4
|
||||
`
|
||||
|
|
@ -670,7 +689,7 @@ WHERE id = $4
|
|||
type UpdateUserParams struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Suspended bool `json:"suspended"`
|
||||
Status string `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
|
|
@ -678,24 +697,26 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
|||
_, err := q.db.Exec(ctx, UpdateUser,
|
||||
arg.FirstName,
|
||||
arg.LastName,
|
||||
arg.Suspended,
|
||||
arg.Status,
|
||||
arg.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateUserOrganization = `-- name: UpdateUserOrganization :exec
|
||||
const UpdateUserStatus = `-- name: UpdateUserStatus :exec
|
||||
UPDATE users
|
||||
SET organization_id = $1
|
||||
SET
|
||||
status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateUserOrganizationParams struct {
|
||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
||||
type UpdateUserStatusParams struct {
|
||||
Status string `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUserOrganization(ctx context.Context, arg UpdateUserOrganizationParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateUserOrganization, arg.OrganizationID, arg.ID)
|
||||
func (q *Queries) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateUserStatus, arg.Status, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ var (
|
|||
// Enabled bool `mapstructure:"Enabled"`
|
||||
// }
|
||||
|
||||
type AFROSMSConfig struct {
|
||||
AfroSMSIdentifierID string `mapstructure:"afro_sms_identifier_id"`
|
||||
AfroSMSAPIKey string `mapstructure:"afro_sms_api_key"`
|
||||
AfroSMSBaseURL string `mapstructure:"afro_sms_base_url"`
|
||||
}
|
||||
|
||||
// type AtlasConfig struct {
|
||||
// BaseURL string `mapstructure:"ATLAS_BASE_URL"`
|
||||
// SecretKey string `mapstructure:"ATLAS_SECRET_KEY"`
|
||||
|
|
@ -120,6 +126,7 @@ type TELEBIRRConfig struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
AFROSMSConfig AFROSMSConfig `mapstructure:"afro_sms_config"`
|
||||
APP_VERSION string
|
||||
FIXER_API_KEY string
|
||||
FIXER_BASE_URL string
|
||||
|
|
@ -248,6 +255,11 @@ func (c *Config) loadEnv() error {
|
|||
return ErrInvalidLevel
|
||||
}
|
||||
|
||||
//Afro SMS
|
||||
c.AFROSMSConfig.AfroSMSAPIKey = os.Getenv("AFRO_SMS_API_KEY")
|
||||
c.AFROSMSConfig.AfroSMSIdentifierID = os.Getenv("AFRO_SMS_IDENTIFIER_ID")
|
||||
c.AFROSMSConfig.AfroSMSBaseURL = os.Getenv("AFRO_SMS_BASE_URL")
|
||||
|
||||
//Telebirr
|
||||
c.TELEBIRR.TelebirrBaseURL = os.Getenv("TELEBIRR_BASE_URL")
|
||||
c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_APP_SECRET")
|
||||
|
|
@ -388,7 +400,7 @@ func (c *Config) loadEnv() error {
|
|||
|
||||
c.ADRO_SMS_HOST_URL = os.Getenv("ADRO_SMS_HOST_URL")
|
||||
if c.ADRO_SMS_HOST_URL == "" {
|
||||
c.ADRO_SMS_HOST_URL = "https://api.afrosms.com"
|
||||
c.ADRO_SMS_HOST_URL = "https://api.afromessage.com"
|
||||
}
|
||||
|
||||
//Atlas
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ const (
|
|||
OtpMediumSms OtpMedium = "sms"
|
||||
)
|
||||
|
||||
|
||||
|
||||
type Otp struct {
|
||||
ID int64
|
||||
UserName string
|
||||
SentTo string
|
||||
Medium OtpMedium
|
||||
For OtpFor
|
||||
|
|
@ -39,3 +38,12 @@ type Otp struct {
|
|||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type VerifyOtpReq struct {
|
||||
UserName string `json:"user_name" validate:"required"`
|
||||
Otp string `json:"otp" validate:"required"`
|
||||
}
|
||||
|
||||
type ResendOtpReq struct {
|
||||
UserName string `json:"user_name" validate:"required"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,34 +6,79 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrUserNotVerified = errors.New("user not verified")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrEmailAlreadyRegistered = errors.New("email is already registered")
|
||||
ErrPhoneAlreadyRegistered = errors.New("phone number is already registered")
|
||||
)
|
||||
|
||||
/*
|
||||
UserStatus reflects the lifecycle state of a user account.
|
||||
Matches DB column: users.status
|
||||
*/
|
||||
type UserStatus string
|
||||
|
||||
const (
|
||||
UserStatusPending UserStatus = "PENDING"
|
||||
UserStatusActive UserStatus = "ACTIVE"
|
||||
UserStatusSuspended UserStatus = "SUSPENDED"
|
||||
UserStatusDeactivated UserStatus = "DEACTIVATED"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
UserName string
|
||||
Email string
|
||||
PhoneNumber string
|
||||
Password []byte
|
||||
Role Role
|
||||
|
||||
Age int
|
||||
EducationLevel string
|
||||
Country string
|
||||
Region string
|
||||
|
||||
EmailVerified bool
|
||||
PhoneVerified bool
|
||||
Suspended bool
|
||||
SuspendedAt time.Time
|
||||
OrganizationID ValidInt64
|
||||
Status UserStatus
|
||||
|
||||
LastLogin *time.Time
|
||||
ProfileCompleted bool
|
||||
ProfilePictureURL string
|
||||
PreferredLanguage string
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
UpdatedAt *time.Time
|
||||
}
|
||||
|
||||
type UserProfileResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
UserName string `json:"user_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
PhoneNumber string `json:"phone_number,omitempty"`
|
||||
Role Role `json:"role"`
|
||||
Age int `json:"age,omitempty"`
|
||||
EducationLevel string `json:"education_level,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
Status UserStatus `json:"status"`
|
||||
LastLogin *time.Time `json:"last_login,omitempty"`
|
||||
ProfileCompleted bool `json:"profile_completed"`
|
||||
ProfilePictureURL string `json:"profile_picture_url,omitempty"`
|
||||
PreferredLanguage string `json:"preferred_language,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type UserFilter struct {
|
||||
Role string
|
||||
OrganizationID ValidInt64
|
||||
|
||||
Page ValidInt
|
||||
PageSize ValidInt
|
||||
Query ValidString
|
||||
|
|
@ -44,90 +89,60 @@ type UserFilter struct {
|
|||
type RegisterUserReq struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
UserName string
|
||||
Email string
|
||||
PhoneNumber string
|
||||
Password string
|
||||
Role string
|
||||
Otp string
|
||||
ReferralCode string `json:"referral_code"`
|
||||
|
||||
OtpMedium OtpMedium
|
||||
OrganizationID ValidInt64
|
||||
|
||||
Age int
|
||||
EducationLevel string
|
||||
Country string
|
||||
Region string
|
||||
PreferredLanguage string
|
||||
}
|
||||
|
||||
type CreateUserReq struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
UserName string
|
||||
Email string
|
||||
PhoneNumber string
|
||||
Password string
|
||||
Role string
|
||||
Suspended bool
|
||||
OrganizationID ValidInt64
|
||||
|
||||
Status UserStatus
|
||||
|
||||
Age int
|
||||
EducationLevel string
|
||||
Country string
|
||||
Region string
|
||||
PreferredLanguage string
|
||||
}
|
||||
|
||||
type ResetPasswordReq struct {
|
||||
Email string
|
||||
PhoneNumber string
|
||||
UserName string
|
||||
Password string
|
||||
Otp string
|
||||
OtpMedium OtpMedium
|
||||
OrganizationID int64
|
||||
OtpCode string
|
||||
}
|
||||
|
||||
type UpdateUserReq struct {
|
||||
UserID int64
|
||||
|
||||
FirstName ValidString
|
||||
LastName ValidString
|
||||
NickName ValidString
|
||||
Suspended ValidBool
|
||||
OrganizationID ValidInt64
|
||||
UserName ValidString
|
||||
|
||||
Status ValidString
|
||||
|
||||
Age ValidInt
|
||||
EducationLevel ValidString
|
||||
Country ValidString
|
||||
Region ValidString
|
||||
|
||||
ProfileCompleted ValidBool
|
||||
ProfilePictureURL ValidString
|
||||
PreferredLanguage ValidString
|
||||
}
|
||||
|
||||
type UpdateUserReferralCode struct {
|
||||
UserID int64
|
||||
Code string
|
||||
}
|
||||
|
||||
// ValidInt64 wraps int64 for optional values
|
||||
// type ValidInt64 struct {
|
||||
// Value int64
|
||||
// Valid bool
|
||||
// }
|
||||
|
||||
// // ValidInt wraps int for optional values
|
||||
// type ValidInt struct {
|
||||
// Value int
|
||||
// Valid bool
|
||||
// }
|
||||
|
||||
// // ValidString wraps string for optional values
|
||||
// type ValidString struct {
|
||||
// Value string
|
||||
// Valid bool
|
||||
// }
|
||||
|
||||
// // ValidBool wraps bool for optional values
|
||||
// type ValidBool struct {
|
||||
// Value bool
|
||||
// Valid bool
|
||||
// }
|
||||
|
||||
// // ValidTime wraps time.Time for optional values
|
||||
// type ValidTime struct {
|
||||
// Value time.Time
|
||||
// Valid bool
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -8,33 +8,50 @@ import (
|
|||
)
|
||||
|
||||
type UserStore interface {
|
||||
CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error)
|
||||
CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error)
|
||||
GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||
IsUserNameUnique(ctx context.Context, userName string) (bool, error)
|
||||
IsUserPending(ctx context.Context, UserName string) (bool, error)
|
||||
GetUserByUserName(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) (domain.User, error)
|
||||
CreateUser(
|
||||
ctx context.Context,
|
||||
user domain.User,
|
||||
usedOtpId int64,
|
||||
) (domain.User, error)
|
||||
CreateUserWithoutOtp(
|
||||
ctx context.Context,
|
||||
user domain.User,
|
||||
) (domain.User, error)
|
||||
GetUserByID(
|
||||
ctx context.Context,
|
||||
id int64,
|
||||
) (domain.User, error)
|
||||
GetAllUsers(
|
||||
ctx context.Context,
|
||||
role *string,
|
||||
organizationID *int64,
|
||||
query *string,
|
||||
createdBefore, createdAfter *time.Time,
|
||||
limit, offset int32,
|
||||
) ([]domain.User, int64, error)
|
||||
GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error)
|
||||
SearchUserByNameOrPhone(ctx context.Context, search string, organizationID *int64, role *string) ([]domain.User, error)
|
||||
GetTotalUsers(ctx context.Context, role *string) (int64, error)
|
||||
SearchUserByNameOrPhone(
|
||||
ctx context.Context,
|
||||
search string,
|
||||
role *string,
|
||||
) ([]domain.User, error)
|
||||
UpdateUser(ctx context.Context, user domain.User) error
|
||||
UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error
|
||||
SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error
|
||||
DeleteUser(ctx context.Context, userID int64) error
|
||||
CheckPhoneEmailExist(ctx context.Context, phone, email string, organizationID domain.ValidInt64) (phoneExists, emailExists bool, err error)
|
||||
CheckPhoneEmailExist(ctx context.Context, phone, email string) (phoneExists, emailExists bool, err error)
|
||||
GetUserByEmailPhone(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
phone string,
|
||||
organizationID domain.ValidInt64,
|
||||
) (domain.User, error)
|
||||
UpdatePassword(ctx context.Context, password, email, phone string, organizationID int64, updatedAt time.Time) error
|
||||
GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error)
|
||||
UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
UpdatePassword(ctx context.Context, password, email, phone string, updatedAt time.Time) error
|
||||
// GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error)
|
||||
// GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error)
|
||||
// UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
|
||||
// UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||
// UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
|
|
@ -57,6 +74,8 @@ type EmailGateway interface {
|
|||
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||
}
|
||||
type OtpStore interface {
|
||||
UpdateExpiredOtp(ctx context.Context, otp, userName string) error
|
||||
MarkOtpAsUsed(ctx context.Context, otp domain.Otp) error
|
||||
CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||
GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
||||
GetOtp(ctx context.Context, userName string) (domain.Otp, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
dbgen "Yimaru-Backend/gen/db"
|
||||
"Yimaru-Backend/internal/domain"
|
||||
|
|
@ -73,19 +74,22 @@ func (s *Store) RevokeRefreshToken(ctx context.Context, token string) error {
|
|||
}
|
||||
|
||||
// GetUserByEmailOrPhone retrieves a user by email or phone number and optional organization ID
|
||||
func (s *Store) GetUserByEmailOrPhone(ctx context.Context, email, phone string, organizationID *int64) (domain.User, error) {
|
||||
// prepare organizationID param for the query
|
||||
// var orgParam pgtype.Int8
|
||||
// if organizationID != nil {
|
||||
// orgParam = pgtype.Int8{Int64: *organizationID}
|
||||
// } else {
|
||||
// orgParam = pgtype.Int8{Status: pgtype.Null}
|
||||
// }
|
||||
func (s *Store) GetUserByEmailOrPhone(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
phone string,
|
||||
) (domain.User, error) {
|
||||
|
||||
u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||
Email: pgtype.Text{String: email, Valid: email != ""},
|
||||
PhoneNumber: pgtype.Text{String: phone, Valid: phone != ""},
|
||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
||||
Email: pgtype.Text{
|
||||
String: email,
|
||||
Valid: email != "",
|
||||
},
|
||||
PhoneNumber: pgtype.Text{
|
||||
String: phone,
|
||||
Valid: phone != "",
|
||||
},
|
||||
// OrganizationID: pgtype.Int8{Int64: organizationID},
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
|
@ -94,20 +98,45 @@ func (s *Store) GetUserByEmailOrPhone(ctx context.Context, email, phone string,
|
|||
return domain.User{}, err
|
||||
}
|
||||
|
||||
var lastLogin *time.Time
|
||||
if u.LastLogin.Valid {
|
||||
lastLogin = &u.LastLogin.Time
|
||||
}
|
||||
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Role: domain.Role(u.Role),
|
||||
Password: u.Password,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Suspended: u.Suspended,
|
||||
SuspendedAt: u.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
LastLogin: lastLogin,
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
ProfilePictureURL: u.ProfilePictureUrl.String,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: u.UpdatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package repository
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
dbgen "Yimaru-Backend/gen/db"
|
||||
"Yimaru-Backend/internal/domain"
|
||||
|
|
@ -155,7 +156,7 @@ func mapDBToDomain(db *dbgen.Notification) *domain.Notification {
|
|||
}
|
||||
|
||||
return &domain.Notification{
|
||||
ID: string(db.ID),
|
||||
ID: strconv.FormatInt(db.ID, 10),
|
||||
RecipientID: db.UserID,
|
||||
Type: domain.NotificationType(db.Type),
|
||||
Level: domain.NotificationLevel(db.Level),
|
||||
|
|
|
|||
|
|
@ -4,16 +4,29 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
dbgen "Yimaru-Backend/gen/db"
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"Yimaru-Backend/internal/ports"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
// Interface for creating new otp store
|
||||
func NewOTPStore(s *Store) ports.OtpStore { return s }
|
||||
|
||||
func (s *Store) UpdateExpiredOtp(ctx context.Context, otp, userName string) error {
|
||||
return s.queries.UpdateExpiredOtp(ctx, dbgen.UpdateExpiredOtpParams{
|
||||
UserName: userName,
|
||||
Otp: otp,
|
||||
ExpiresAt: pgtype.Timestamptz{
|
||||
Time: time.Now().Add(5 * time.Minute),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error {
|
||||
return s.queries.CreateOtp(ctx, dbgen.CreateOtpParams{
|
||||
SentTo: otp.SentTo,
|
||||
|
|
@ -30,14 +43,10 @@ func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error {
|
|||
},
|
||||
})
|
||||
}
|
||||
func (s *Store) GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error) {
|
||||
row, err := s.queries.GetOtp(ctx, dbgen.GetOtpParams{
|
||||
SentTo: sentTo,
|
||||
Medium: string(medium),
|
||||
OtpFor: string(sentfor),
|
||||
})
|
||||
func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error) {
|
||||
row, err := s.queries.GetOtp(ctx, userName)
|
||||
if err != nil {
|
||||
fmt.Printf("OTP REPO error: %v sentTo: %v, medium: %v, otpFor: %v\n", err, sentTo, medium, sentfor)
|
||||
fmt.Printf("OTP REPO error: %v userName: %v\n", err, userName)
|
||||
if err == sql.ErrNoRows {
|
||||
return domain.Otp{}, domain.ErrOtpNotFound
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,75 +14,109 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
// type Store struct {
|
||||
// db *pgxpool.Pool
|
||||
// queries *dbgen.Queries
|
||||
// }
|
||||
|
||||
// func NewStore(db *pgxpool.Pool) *Store {
|
||||
// return &Store{
|
||||
// db: db,
|
||||
// queries: dbgen.New(db),
|
||||
// }
|
||||
// }
|
||||
|
||||
func NewUserStore(s *Store) ports.UserStore { return s }
|
||||
|
||||
func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) {
|
||||
func (s *Store) IsUserPending(ctx context.Context, UserName string) (bool, error) {
|
||||
isPending, err := s.queries.IsUserPending(ctx, UserName)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, authentication.ErrUserNotFound
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return isPending, nil
|
||||
}
|
||||
|
||||
func (s *Store) IsUserNameUnique(ctx context.Context, userName string) (bool, error) {
|
||||
isUnique, err := s.queries.IsUserNameUnique(ctx, userName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isUnique, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateUserWithoutOtp(
|
||||
ctx context.Context,
|
||||
user domain.User,
|
||||
) (domain.User, error) {
|
||||
|
||||
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: pgtype.Text{String: user.NickName},
|
||||
UserName: user.UserName,
|
||||
|
||||
Email: pgtype.Text{String: user.Email, Valid: user.Email != ""},
|
||||
PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""},
|
||||
|
||||
Role: string(user.Role),
|
||||
Password: user.Password,
|
||||
|
||||
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
||||
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
||||
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
||||
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
||||
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
Suspended: user.Suspended,
|
||||
SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()},
|
||||
OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid},
|
||||
CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
|
||||
Status: string(user.Status),
|
||||
ProfileCompleted: user.ProfileCompleted,
|
||||
PreferredLanguage: pgtype.Text{
|
||||
String: user.PreferredLanguage,
|
||||
Valid: user.PreferredLanguage != "",
|
||||
},
|
||||
|
||||
// OrganizationID: user.OrganizationID.ToPG(),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
var updatedAt *time.Time
|
||||
if userRes.UpdatedAt.Valid {
|
||||
updatedAt = &userRes.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: userRes.ID,
|
||||
FirstName: userRes.FirstName,
|
||||
LastName: userRes.LastName,
|
||||
NickName: userRes.NickName.String,
|
||||
UserName: userRes.UserName,
|
||||
Email: userRes.Email.String,
|
||||
PhoneNumber: userRes.PhoneNumber.String,
|
||||
Role: domain.Role(userRes.Role),
|
||||
Password: user.Password,
|
||||
|
||||
Age: int(userRes.Age.Int32),
|
||||
EducationLevel: userRes.EducationLevel.String,
|
||||
Country: userRes.Country.String,
|
||||
Region: userRes.Region.String,
|
||||
|
||||
EmailVerified: userRes.EmailVerified,
|
||||
PhoneVerified: userRes.PhoneVerified,
|
||||
Suspended: userRes.Suspended,
|
||||
SuspendedAt: userRes.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
|
||||
Status: domain.UserStatus(userRes.Status),
|
||||
|
||||
ProfileCompleted: userRes.ProfileCompleted,
|
||||
PreferredLanguage: userRes.PreferredLanguage.String,
|
||||
|
||||
CreatedAt: userRes.CreatedAt.Time,
|
||||
UpdatedAt: userRes.UpdatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateUser inserts a new user into the database
|
||||
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,
|
||||
) (domain.User, error) {
|
||||
|
||||
// Optional: mark OTP as used
|
||||
if usedOtpId > 0 {
|
||||
err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
|
||||
if err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
|
||||
ID: usedOtpId,
|
||||
UsedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
}
|
||||
|
|
@ -90,53 +124,74 @@ func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int6
|
|||
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: pgtype.Text{String: user.NickName},
|
||||
UserName: user.UserName,
|
||||
|
||||
Email: pgtype.Text{String: user.Email, Valid: user.Email != ""},
|
||||
PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""},
|
||||
|
||||
Role: string(user.Role),
|
||||
Password: user.Password,
|
||||
|
||||
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
||||
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
||||
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
||||
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
||||
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
Suspended: user.Suspended,
|
||||
SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()},
|
||||
OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid},
|
||||
CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
|
||||
Status: string(user.Status),
|
||||
ProfileCompleted: user.ProfileCompleted,
|
||||
PreferredLanguage: pgtype.Text{
|
||||
String: user.PreferredLanguage,
|
||||
Valid: user.PreferredLanguage != "",
|
||||
},
|
||||
|
||||
// OrganizationID: user.OrganizationID.ToPG(),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
var updatedAt *time.Time
|
||||
if userRes.UpdatedAt.Valid {
|
||||
updatedAt = &userRes.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: userRes.ID,
|
||||
FirstName: userRes.FirstName,
|
||||
LastName: userRes.LastName,
|
||||
NickName: userRes.NickName.String,
|
||||
UserName: userRes.UserName,
|
||||
Email: userRes.Email.String,
|
||||
PhoneNumber: userRes.PhoneNumber.String,
|
||||
Role: domain.Role(userRes.Role),
|
||||
Password: user.Password,
|
||||
|
||||
Age: int(userRes.Age.Int32),
|
||||
EducationLevel: userRes.EducationLevel.String,
|
||||
Country: userRes.Country.String,
|
||||
Region: userRes.Region.String,
|
||||
|
||||
EmailVerified: userRes.EmailVerified,
|
||||
PhoneVerified: userRes.PhoneVerified,
|
||||
Suspended: userRes.Suspended,
|
||||
SuspendedAt: userRes.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
|
||||
CreatedAt: userRes.CreatedAt.Time,
|
||||
UpdatedAt: userRes.UpdatedAt.Time,
|
||||
}, nil
|
||||
Status: domain.UserStatus(userRes.Status),
|
||||
|
||||
ProfileCompleted: userRes.ProfileCompleted,
|
||||
PreferredLanguage: userRes.PreferredLanguage.String,
|
||||
|
||||
CreatedAt: userRes.CreatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserByID retrieves a user by ID
|
||||
func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||
userRes, err := s.queries.GetUserByID(ctx, id)
|
||||
func (s *Store) GetUserByID(
|
||||
ctx context.Context,
|
||||
id int64,
|
||||
) (domain.User, error) {
|
||||
|
||||
u, err := s.queries.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.User{}, domain.ErrUserNotFound
|
||||
|
|
@ -144,34 +199,52 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
|||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: userRes.ID,
|
||||
FirstName: userRes.FirstName,
|
||||
LastName: userRes.LastName,
|
||||
NickName: userRes.NickName.String,
|
||||
Email: userRes.Email.String,
|
||||
PhoneNumber: userRes.PhoneNumber.String,
|
||||
Role: domain.Role(userRes.Role),
|
||||
Age: int(userRes.Age.Int32),
|
||||
EducationLevel: userRes.EducationLevel.String,
|
||||
Country: userRes.Country.String,
|
||||
Region: userRes.Region.String,
|
||||
EmailVerified: userRes.EmailVerified,
|
||||
PhoneVerified: userRes.PhoneVerified,
|
||||
Suspended: userRes.Suspended,
|
||||
SuspendedAt: userRes.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
|
||||
CreatedAt: userRes.CreatedAt.Time,
|
||||
UpdatedAt: userRes.UpdatedAt.Time,
|
||||
}, nil
|
||||
var lastLogin *time.Time
|
||||
if u.LastLogin.Valid {
|
||||
lastLogin = &u.LastLogin.Time
|
||||
}
|
||||
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
LastLogin: lastLogin,
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
ProfilePictureURL: u.ProfilePictureUrl.String,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAllUsers retrieves users with optional filters
|
||||
func (s *Store) GetAllUsers(
|
||||
ctx context.Context,
|
||||
role *string,
|
||||
organizationID *int64,
|
||||
query *string,
|
||||
createdBefore, createdAfter *time.Time,
|
||||
limit, offset int32,
|
||||
|
|
@ -186,9 +259,9 @@ func (s *Store) GetAllUsers(
|
|||
params.Role = *role
|
||||
}
|
||||
|
||||
if organizationID != nil {
|
||||
params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true}
|
||||
}
|
||||
// if organizationID != nil {
|
||||
// params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true}
|
||||
// }
|
||||
|
||||
if query != nil {
|
||||
params.Query = pgtype.Text{String: *query, Valid: true}
|
||||
|
|
@ -212,31 +285,42 @@ func (s *Store) GetAllUsers(
|
|||
}
|
||||
|
||||
totalCount := rows[0].TotalCount
|
||||
|
||||
users := make([]domain.User, 0, len(rows))
|
||||
|
||||
for _, u := range rows {
|
||||
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
users = append(users, domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
NickName: u.NickName.String,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Suspended: u.Suspended,
|
||||
SuspendedAt: u.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{
|
||||
Value: u.OrganizationID.Int64,
|
||||
Valid: u.OrganizationID.Valid,
|
||||
},
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: u.UpdatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -244,11 +328,8 @@ func (s *Store) GetAllUsers(
|
|||
}
|
||||
|
||||
// GetTotalUsers counts users with optional filters
|
||||
func (s *Store) GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error) {
|
||||
count, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{
|
||||
Role: *role,
|
||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
||||
})
|
||||
func (s *Store) GetTotalUsers(ctx context.Context, role *string) (int64, error) {
|
||||
count, err := s.queries.GetTotalUsers(ctx, *role)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -256,38 +337,73 @@ func (s *Store) GetTotalUsers(ctx context.Context, role *string, organizationID
|
|||
}
|
||||
|
||||
// SearchUserByNameOrPhone searches users by name or phone
|
||||
func (s *Store) SearchUserByNameOrPhone(ctx context.Context, search string, organizationID *int64, role *string) ([]domain.User, error) {
|
||||
rows, err := s.queries.SearchUserByNameOrPhone(ctx, dbgen.SearchUserByNameOrPhoneParams{
|
||||
Column1: pgtype.Text{String: search},
|
||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
||||
Role: pgtype.Text{String: *role},
|
||||
})
|
||||
func (s *Store) SearchUserByNameOrPhone(
|
||||
ctx context.Context,
|
||||
search string,
|
||||
role *string,
|
||||
) ([]domain.User, error) {
|
||||
|
||||
params := dbgen.SearchUserByNameOrPhoneParams{
|
||||
Column1: pgtype.Text{
|
||||
String: search,
|
||||
Valid: search != "",
|
||||
},
|
||||
}
|
||||
|
||||
// if organizationID != nil {
|
||||
// params.OrganizationID = pgtype.Int8{
|
||||
// Int64: *organizationID,
|
||||
// Valid: true,
|
||||
// }
|
||||
// }
|
||||
|
||||
if role != nil {
|
||||
params.Role = pgtype.Text{
|
||||
String: *role,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := s.queries.SearchUserByNameOrPhone(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make([]domain.User, len(rows))
|
||||
for i, u := range rows {
|
||||
users[i] = domain.User{
|
||||
users := make([]domain.User, 0, len(rows))
|
||||
for _, u := range rows {
|
||||
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
users = append(users, domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
NickName: u.NickName.String,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Suspended: u.Suspended,
|
||||
SuspendedAt: u.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: u.UpdatedAt.Time,
|
||||
}
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return users, nil
|
||||
|
|
@ -296,29 +412,29 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, search string, orga
|
|||
// UpdateUser updates basic user info
|
||||
func (s *Store) UpdateUser(ctx context.Context, user domain.User) error {
|
||||
return s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Suspended: user.Suspended,
|
||||
ID: user.ID,
|
||||
Status: string(user.Status),
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUserOrganization updates a user's organization
|
||||
func (s *Store) UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error {
|
||||
return s.queries.UpdateUserOrganization(ctx, dbgen.UpdateUserOrganizationParams{
|
||||
OrganizationID: pgtype.Int8{Int64: organizationID, Valid: true},
|
||||
ID: userID,
|
||||
})
|
||||
}
|
||||
// func (s *Store) UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error {
|
||||
// return s.queries.UpdateUserOrganization(ctx, dbgen.UpdateUserOrganizationParams{
|
||||
// OrganizationID: pgtype.Int8{Int64: organizationID, Valid: true},
|
||||
// ID: userID,
|
||||
// })
|
||||
// }
|
||||
|
||||
// SuspendUser suspends a user
|
||||
func (s *Store) SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error {
|
||||
return s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||
Suspended: suspended,
|
||||
SuspendedAt: pgtype.Timestamptz{Time: suspendedAt, Valid: true},
|
||||
ID: userID,
|
||||
})
|
||||
}
|
||||
// func (s *Store) SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error {
|
||||
// return s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||
// Suspended: suspended,
|
||||
// SuspendedAt: pgtype.Timestamptz{Time: suspendedAt, Valid: true},
|
||||
// ID: userID,
|
||||
// })
|
||||
// }
|
||||
|
||||
// DeleteUser removes a user
|
||||
func (s *Store) DeleteUser(ctx context.Context, userID int64) error {
|
||||
|
|
@ -326,11 +442,10 @@ func (s *Store) DeleteUser(ctx context.Context, userID int64) error {
|
|||
}
|
||||
|
||||
// CheckPhoneEmailExist checks if phone or email exists in an organization
|
||||
func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string, organizationID domain.ValidInt64) (phoneExists, emailExists bool, err error) {
|
||||
func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string) (phoneExists, emailExists bool, err error) {
|
||||
res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{
|
||||
PhoneNumber: pgtype.Text{String: phone},
|
||||
Email: pgtype.Text{String: email},
|
||||
OrganizationID: pgtype.Int8{Int64: organizationID.Value},
|
||||
})
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
|
|
@ -339,15 +454,69 @@ func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string, o
|
|||
return res.PhoneExists, res.EmailExists, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUserByUserName(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) (domain.User, error) {
|
||||
|
||||
u, err := s.queries.GetUserByUserName(ctx, userName)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.User{}, authentication.ErrUserNotFound
|
||||
}
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
var lastLogin *time.Time
|
||||
if u.LastLogin.Valid {
|
||||
lastLogin = &u.LastLogin.Time
|
||||
}
|
||||
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Password: u.Password,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
LastLogin: lastLogin,
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail retrieves a user by email and organization
|
||||
func (s *Store) GetUserByEmailPhone(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
phone string,
|
||||
organizationID domain.ValidInt64,
|
||||
) (domain.User, error) {
|
||||
|
||||
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||
u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||
Email: pgtype.Text{
|
||||
String: email,
|
||||
Valid: email != "",
|
||||
|
|
@ -356,7 +525,6 @@ func (s *Store) GetUserByEmailPhone(
|
|||
String: phone,
|
||||
Valid: phone != "",
|
||||
},
|
||||
OrganizationID: organizationID.ToPG(),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
|
@ -365,87 +533,113 @@ func (s *Store) GetUserByEmailPhone(
|
|||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return domain.User{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: user.NickName.String,
|
||||
Email: user.Email.String,
|
||||
PhoneNumber: user.PhoneNumber.String,
|
||||
Password: user.Password,
|
||||
Role: domain.Role(user.Role),
|
||||
Age: int(user.Age.Int32),
|
||||
EducationLevel: user.EducationLevel.String,
|
||||
Country: user.Country.String,
|
||||
Region: user.Region.String,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
Suspended: user.Suspended,
|
||||
SuspendedAt: user.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{
|
||||
Value: user.OrganizationID.Int64,
|
||||
Valid: user.OrganizationID.Valid,
|
||||
},
|
||||
CreatedAt: user.CreatedAt.Time,
|
||||
UpdatedAt: user.UpdatedAt.Time,
|
||||
}, nil
|
||||
var lastLogin *time.Time
|
||||
if u.LastLogin.Valid {
|
||||
lastLogin = &u.LastLogin.Time
|
||||
}
|
||||
|
||||
// UpdatePassword updates a user's password
|
||||
func (s *Store) UpdatePassword(ctx context.Context, password, email, phone string, organizationID int64, updatedAt time.Time) error {
|
||||
return s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{
|
||||
Password: []byte(password),
|
||||
Email: pgtype.Text{String: email},
|
||||
PhoneNumber: pgtype.Text{String: phone},
|
||||
UpdatedAt: pgtype.Timestamptz{Time: updatedAt},
|
||||
OrganizationID: pgtype.Int8{Int64: organizationID},
|
||||
})
|
||||
var updatedAt *time.Time
|
||||
if u.UpdatedAt.Valid {
|
||||
updatedAt = &u.UpdatedAt.Time
|
||||
}
|
||||
|
||||
// GetOwnerByOrganizationID retrieves the owner user of an organization
|
||||
func (s *Store) GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error) {
|
||||
userRes, err := s.queries.GetOwnerByOrganizationID(ctx, organizationID)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
return mapUser(userRes), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||
ID: id,
|
||||
Suspended: status,
|
||||
SuspendedAt: pgtype.Timestamptz{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapUser converts dbgen.User to domain.User
|
||||
func mapUser(u dbgen.User) domain.User {
|
||||
return domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
NickName: u.NickName.String,
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
Password: u.Password,
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Suspended: u.Suspended,
|
||||
SuspendedAt: u.SuspendedAt.Time,
|
||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
||||
Status: domain.UserStatus(u.Status),
|
||||
|
||||
LastLogin: lastLogin,
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: updatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePassword updates a user's password
|
||||
func (s *Store) UpdatePassword(ctx context.Context, password, email, phone string, updatedAt time.Time) error {
|
||||
return s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{
|
||||
Password: []byte(password),
|
||||
Email: pgtype.Text{String: email},
|
||||
PhoneNumber: pgtype.Text{String: phone},
|
||||
// OrganizationID: pgtype.Int8{Int64: organizationID},
|
||||
})
|
||||
}
|
||||
|
||||
// GetOwnerByOrganizationID retrieves the owner user of an organization
|
||||
// func (s *Store) GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error) {
|
||||
// userRes, err := s.queries.GetOwnerByOrganizationID(ctx, organizationID)
|
||||
// if err != nil {
|
||||
// return domain.User{}, err
|
||||
// }
|
||||
// return mapUser(userRes), nil
|
||||
// }
|
||||
|
||||
// func (s *Store) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
// err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||
// ID: id,
|
||||
// Suspended: status,
|
||||
// SuspendedAt: pgtype.Timestamptz{
|
||||
// Time: time.Now(),
|
||||
// Valid: true,
|
||||
// },
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// mapUser converts dbgen.User to domain.User
|
||||
func MapUser(u dbgen.User) domain.User {
|
||||
|
||||
return domain.User{
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
|
||||
UserName: u.UserName,
|
||||
Email: u.Email.String,
|
||||
PhoneNumber: u.PhoneNumber.String,
|
||||
|
||||
Role: domain.Role(u.Role),
|
||||
|
||||
Age: int(u.Age.Int32),
|
||||
EducationLevel: u.EducationLevel.String,
|
||||
Country: u.Country.String,
|
||||
Region: u.Region.String,
|
||||
|
||||
EmailVerified: u.EmailVerified,
|
||||
PhoneVerified: u.PhoneVerified,
|
||||
Status: domain.UserStatus(u.Status),
|
||||
LastLogin: &u.LastLogin.Time,
|
||||
ProfileCompleted: u.ProfileCompleted,
|
||||
PreferredLanguage: u.PreferredLanguage.String,
|
||||
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: u.OrganizationID.Int64,
|
||||
// Valid: u.OrganizationID.Valid,
|
||||
// },
|
||||
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
UpdatedAt: u.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"Yimaru-Backend/internal/domain"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
|
@ -23,55 +24,65 @@ type LoginSuccess struct {
|
|||
UserId int64
|
||||
Role domain.Role
|
||||
RfToken string
|
||||
CompanyID domain.ValidInt64
|
||||
}
|
||||
|
||||
func (s *Service) Login(ctx context.Context, email, phone string, password string, companyID domain.ValidInt64) (LoginSuccess, error) {
|
||||
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone, companyID)
|
||||
func (s *Service) Login(
|
||||
ctx context.Context,
|
||||
userName, password string,
|
||||
) (LoginSuccess, error) {
|
||||
|
||||
user, err := s.userStore.GetUserByUserName(ctx, userName)
|
||||
if err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
err = matchPassword(password, user.Password)
|
||||
if err != nil {
|
||||
|
||||
if user.Status == domain.UserStatusPending{
|
||||
return LoginSuccess{}, domain.ErrUserNotVerified
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if err := matchPassword(password, user.Password); err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
if user.Suspended {
|
||||
|
||||
// Status check instead of Suspended
|
||||
if user.Status == domain.UserStatusSuspended {
|
||||
return LoginSuccess{}, ErrUserSuspended
|
||||
}
|
||||
|
||||
// Handle existing refresh token
|
||||
oldRefreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
|
||||
|
||||
if err != nil && err != ErrRefreshTokenNotFound {
|
||||
if err != nil && !errors.Is(err, ErrRefreshTokenNotFound) {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
|
||||
// If old refresh token is not revoked, revoke it
|
||||
// Revoke if exists and not revoked
|
||||
if err == nil && !oldRefreshToken.Revoked {
|
||||
err = s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token)
|
||||
if err != nil {
|
||||
if err := s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token); err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new refresh token
|
||||
refreshToken, err := generateRefreshToken()
|
||||
if err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
||||
|
||||
if err := s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
||||
Token: refreshToken,
|
||||
UserID: user.ID,
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
|
||||
// Return login success payload
|
||||
return LoginSuccess{
|
||||
UserId: user.ID,
|
||||
Role: user.Role,
|
||||
RfToken: refreshToken,
|
||||
CompanyID: user.OrganizationID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ package messenger
|
|||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/twilio/twilio-go"
|
||||
|
|
@ -16,28 +21,13 @@ var (
|
|||
)
|
||||
|
||||
// If the company id is valid, it is a company based notification else its a global notification (by the super admin)
|
||||
func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string, companyID domain.ValidInt64) error {
|
||||
func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string) error {
|
||||
|
||||
var settingsList domain.SettingList
|
||||
// var err error
|
||||
|
||||
// if companyID.Valid {
|
||||
// settingsList, err = s.settingSvc.GetOverrideSettingsList(ctx, companyID.Value)
|
||||
// if err != nil {
|
||||
// // TODO: Send a log about the error
|
||||
// return err
|
||||
// }
|
||||
// } else {
|
||||
// settingsList, err = s.settingSvc.GetGlobalSettingList(ctx)
|
||||
// if err != nil {
|
||||
// // TODO: Send a log about the error
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
switch settingsList.SMSProvider {
|
||||
case domain.AfroMessage:
|
||||
return s.SendAfroMessageSMS(ctx, receiverPhone, message)
|
||||
return s.SendAfroMessageSMSLatest(ctx, receiverPhone, message, nil)
|
||||
case domain.TwilioSms:
|
||||
return s.SendTwilioSMS(ctx, receiverPhone, message)
|
||||
default:
|
||||
|
|
@ -73,6 +63,79 @@ func (s *Service) SendAfroMessageSMS(ctx context.Context, receiverPhone, message
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendAfroMessageSMSLatest(
|
||||
ctx context.Context,
|
||||
receiverPhone string,
|
||||
message string,
|
||||
callbackURL *string, // optional
|
||||
) error {
|
||||
|
||||
baseURL := s.config.AFROSMSConfig.AfroSMSBaseURL
|
||||
|
||||
// Build query parameters explicitly
|
||||
params := url.Values{}
|
||||
params.Set("to", receiverPhone)
|
||||
params.Set("message", message)
|
||||
params.Set("sender", s.config.AFRO_SMS_SENDER_NAME)
|
||||
|
||||
// Optional parameters
|
||||
if s.config.AFROSMSConfig.AfroSMSIdentifierID != "" {
|
||||
params.Set("from", s.config.AFROSMSConfig.AfroSMSIdentifierID)
|
||||
}
|
||||
|
||||
if callbackURL != nil {
|
||||
params.Set("callback", *callbackURL)
|
||||
}
|
||||
|
||||
// Construct full URL
|
||||
reqURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// AfroMessage authentication (API key)
|
||||
req.Header.Set("Authorization", "Bearer "+s.config.AFRO_SMS_API_KEY)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf(
|
||||
"afromessage sms failed: status=%d response=%s",
|
||||
resp.StatusCode,
|
||||
string(body),
|
||||
)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ack, ok := result["acknowledge"].(string)
|
||||
if !ok || ack != "success" {
|
||||
return fmt.Errorf("sms delivery failed: %v", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendTwilioSMS(ctx context.Context, receiverPhone, message string) error {
|
||||
accountSid := s.config.TwilioAccountSid
|
||||
authToken := s.config.TwilioAuthToken
|
||||
|
|
|
|||
|
|
@ -9,12 +9,17 @@ import (
|
|||
"Yimaru-Backend/internal/services/user"
|
||||
"Yimaru-Backend/internal/web_server/ws"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
// "errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// "github.com/segmentio/kafka-go"
|
||||
"go.uber.org/zap"
|
||||
// afro "github.com/amanuelabay/afrosms-go"
|
||||
|
|
@ -67,6 +72,79 @@ func New(
|
|||
return svc
|
||||
}
|
||||
|
||||
func (s *Service) SendAfroMessageSMSTemp(
|
||||
ctx context.Context,
|
||||
receiverPhone string,
|
||||
message string,
|
||||
callbackURL *string, // optional
|
||||
) error {
|
||||
|
||||
baseURL := s.config.AFROSMSConfig.AfroSMSBaseURL
|
||||
|
||||
// Build query parameters explicitly
|
||||
params := url.Values{}
|
||||
params.Set("to", receiverPhone)
|
||||
params.Set("message", message)
|
||||
params.Set("sender", s.config.AFRO_SMS_SENDER_NAME)
|
||||
|
||||
// Optional parameters
|
||||
if s.config.AFROSMSConfig.AfroSMSIdentifierID != "" {
|
||||
params.Set("from", s.config.AFROSMSConfig.AfroSMSIdentifierID)
|
||||
}
|
||||
|
||||
if callbackURL != nil {
|
||||
params.Set("callback", *callbackURL)
|
||||
}
|
||||
|
||||
// Construct full URL
|
||||
reqURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// AfroMessage authentication (API key)
|
||||
req.Header.Set("Authorization", "Bearer "+s.config.AFRO_SMS_API_KEY)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf(
|
||||
"afromessage sms failed: status=%d response=%s",
|
||||
resp.StatusCode,
|
||||
string(body),
|
||||
)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ack, ok := result["acknowledge"].(string)
|
||||
if !ok || ack != "success" {
|
||||
return fmt.Errorf("sms delivery failed: %v", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) addConnection(recipientID int64, c *websocket.Conn) error {
|
||||
if c == nil {
|
||||
s.mongoLogger.Warn("[NotificationSvc.AddConnection] Attempted to add nil WebSocket connection",
|
||||
|
|
@ -334,13 +412,12 @@ func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, me
|
|||
if user.PhoneNumber == "" {
|
||||
return fmt.Errorf("phone Number is invalid")
|
||||
}
|
||||
err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message, user.OrganizationID)
|
||||
err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS",
|
||||
zap.Int64("recipient_id", recipientID),
|
||||
zap.String("user_phone_number", user.PhoneNumber),
|
||||
zap.String("message", message),
|
||||
zap.Int64("company_id", user.OrganizationID.Value),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -371,7 +448,6 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64,
|
|||
zap.Int64("recipient_id", recipientID),
|
||||
zap.String("user_email", user.Email),
|
||||
zap.String("message", message),
|
||||
zap.Int64("company_id", user.OrganizationID.Value),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,51 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (s *Service) ResendOtp(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) error {
|
||||
|
||||
otpCode := helpers.GenerateOTP()
|
||||
|
||||
message := fmt.Sprintf(
|
||||
"Welcome to Yimaru Online Learning Platform, your OTP is %s please don't share with anyone.",
|
||||
otpCode,
|
||||
)
|
||||
|
||||
otp, err := s.otpStore.GetOtp(ctx, userName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Broadcast OTP (same logic as SendOtp)
|
||||
switch otp.Medium {
|
||||
case domain.OtpMediumSms:
|
||||
if err := s.messengerSvc.SendAfroMessageSMS(ctx, otp.SentTo, message); err != nil {
|
||||
return err
|
||||
}
|
||||
case domain.OtpMediumEmail:
|
||||
if err := s.messengerSvc.SendEmail(
|
||||
ctx,
|
||||
otp.SentTo,
|
||||
message,
|
||||
message,
|
||||
"Yimaru - One Time Password",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid otp medium: %s", otp.Medium)
|
||||
}
|
||||
|
||||
if err := s.otpStore.UpdateExpiredOtp(ctx, otp.Otp, userName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
|
||||
otpCode := helpers.GenerateOTP()
|
||||
|
||||
|
|
@ -49,6 +94,11 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF
|
|||
return s.otpStore.CreateOtp(ctx, otp)
|
||||
}
|
||||
|
||||
// helper function to get a pointer to time.Time
|
||||
func timePtr(t time.Time) time.Time {
|
||||
return t
|
||||
}
|
||||
|
||||
func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,36 +5,35 @@ import (
|
|||
"context"
|
||||
)
|
||||
|
||||
func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq, is_company bool) (domain.User, error) {
|
||||
// Create User
|
||||
// creator, err := s.userStore.GetUserByID(ctx, createrUserId)
|
||||
// if err != nil {
|
||||
// return domain.User{}, err
|
||||
// }
|
||||
// if creator.Role != domain.RoleAdmin {
|
||||
// User.BranchID = creator.BranchID
|
||||
// User.Role = string(domain.RoleCashier)
|
||||
// } else {
|
||||
// User.BranchID = branchId
|
||||
// User.Role = string(domain.RoleBranchManager)
|
||||
// }
|
||||
func (s *Service) CreateUser(
|
||||
ctx context.Context,
|
||||
req domain.CreateUserReq,
|
||||
isCompany bool,
|
||||
) (domain.User, error) {
|
||||
|
||||
hashedPassword, err := hashPassword(User.Password)
|
||||
// Hash the password
|
||||
hashedPassword, err := hashPassword(req.Password)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
// Create the user
|
||||
return s.userStore.CreateUserWithoutOtp(ctx, domain.User{
|
||||
FirstName: User.FirstName,
|
||||
LastName: User.LastName,
|
||||
Email: User.Email,
|
||||
PhoneNumber: User.PhoneNumber,
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
UserName: req.UserName,
|
||||
Email: req.Email,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
Password: hashedPassword,
|
||||
Role: domain.Role(User.Role),
|
||||
EmailVerified: true,
|
||||
Role: domain.Role(req.Role),
|
||||
EmailVerified: true, // assuming auto-verified on creation
|
||||
PhoneVerified: true,
|
||||
Suspended: User.Suspended,
|
||||
OrganizationID: User.OrganizationID,
|
||||
Status: domain.UserStatusActive,
|
||||
Age: req.Age,
|
||||
EducationLevel: req.EducationLevel,
|
||||
Country: req.Country,
|
||||
Region: req.Region,
|
||||
ProfileCompleted: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
|
|||
|
||||
func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
|
||||
// Get all Users
|
||||
return s.userStore.GetAllUsers(ctx, &filter.Role, &filter.OrganizationID.Value, &filter.Query.Value, &filter.CreatedBefore.Value, &filter.CreatedAfter.Value, int32(filter.PageSize.Value), int32(filter.Page.Value))
|
||||
return s.userStore.GetAllUsers(ctx, &filter.Role, &filter.Query.Value, &filter.CreatedBefore.Value, &filter.CreatedAfter.Value, int32(filter.PageSize.Value), int32(filter.Page.Value))
|
||||
}
|
||||
func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +1,48 @@
|
|||
package user
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"context"
|
||||
)
|
||||
|
||||
// )
|
||||
type UserStore interface {
|
||||
IsUserPending(ctx context.Context, userName string) (bool, error)
|
||||
GetUserByUserName(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) (domain.User, error)
|
||||
VerifyOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, otpCode string) error
|
||||
CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error)
|
||||
CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
|
||||
GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||
GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error)
|
||||
GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error)
|
||||
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||
UpdateUserCompany(ctx context.Context, id int64, companyID int64) error
|
||||
UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
DeleteUser(ctx context.Context, id int64) error
|
||||
CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error)
|
||||
GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
|
||||
GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
|
||||
SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
|
||||
UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
|
||||
|
||||
// type UserStore interface {
|
||||
// CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error)
|
||||
// CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
|
||||
// GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||
// GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error)
|
||||
// GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error)
|
||||
// GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error)
|
||||
// GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
|
||||
// GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error)
|
||||
// UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||
// UpdateUserCompany(ctx context.Context, id int64, companyID int64) error
|
||||
// UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
||||
// DeleteUser(ctx context.Context, id int64) error
|
||||
// CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error)
|
||||
// GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
|
||||
// GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
|
||||
// SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
|
||||
// UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
|
||||
|
||||
// GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
// GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error)
|
||||
// GetBranchCustomerCounts(ctx context.Context, filter domain.ReportFilter) (map[int64]int64, error)
|
||||
// GetRoleCounts(ctx context.Context, role string, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
// }
|
||||
// type SmsGateway interface {
|
||||
// SendSMSOTP(ctx context.Context, phoneNumber, otp string) error
|
||||
// }
|
||||
// type EmailGateway interface {
|
||||
// SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||
// }
|
||||
// type OtpStore interface {
|
||||
// CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||
// GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
||||
// }
|
||||
GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error)
|
||||
GetBranchCustomerCounts(ctx context.Context, filter domain.ReportFilter) (map[int64]int64, error)
|
||||
GetRoleCounts(ctx context.Context, role string, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
}
|
||||
type SmsGateway interface {
|
||||
SendSMSOTP(ctx context.Context, phoneNumber, otp string) error
|
||||
}
|
||||
type EmailGateway interface {
|
||||
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||
}
|
||||
type OtpStore interface {
|
||||
ResendOtp(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) error
|
||||
CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||
GetOtp(ctx context.Context, userName string) (domain.Otp, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,51 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error) { // email,phone,error
|
||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email, companyID)
|
||||
func (s *Service) VerifyOtp(ctx context.Context, userName string, otpCode string) error {
|
||||
// 1. Retrieve the OTP from the store
|
||||
storedOtp, err := s.otpStore.GetOtp(ctx, userName)
|
||||
if err != nil {
|
||||
return err // could be ErrOtpNotFound or other DB errors
|
||||
}
|
||||
|
||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider, companyID domain.ValidInt64) error {
|
||||
// 2. Check if OTP was already used
|
||||
if storedOtp.Used {
|
||||
return domain.ErrOtpAlreadyUsed
|
||||
}
|
||||
|
||||
// 3. Check if OTP has expired
|
||||
if time.Now().After(storedOtp.ExpiresAt) {
|
||||
return domain.ErrOtpExpired
|
||||
}
|
||||
|
||||
// 4. Check if the provided OTP matches
|
||||
if storedOtp.Otp != otpCode {
|
||||
return domain.ErrInvalidOtp
|
||||
}
|
||||
|
||||
// 5. Mark OTP as used
|
||||
storedOtp.Used = true
|
||||
storedOtp.UsedAt = timePtr(time.Now())
|
||||
|
||||
if err := s.otpStore.MarkOtpAsUsed(ctx, storedOtp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
||||
}
|
||||
|
||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
var err error
|
||||
// check if user exists
|
||||
switch medium {
|
||||
case domain.OtpMediumEmail:
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "")
|
||||
case domain.OtpMediumSms:
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo)
|
||||
}
|
||||
|
||||
if err != nil && err != domain.ErrUserNotFound {
|
||||
|
|
@ -29,52 +62,68 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium,
|
|||
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
||||
// get otp
|
||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) {
|
||||
// Check if the email or phone is already registered based on OTP medium
|
||||
phoneExists, emailExists, err := s.userStore.CheckPhoneEmailExist(ctx, registerReq.PhoneNumber, registerReq.Email)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
||||
if emailExists {
|
||||
return domain.User{}, domain.ErrEmailAlreadyRegistered
|
||||
}
|
||||
} else {
|
||||
if phoneExists {
|
||||
return domain.User{}, domain.ErrPhoneAlreadyRegistered
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
hashedPassword, err := hashPassword(registerReq.Password)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
// Prepare the user
|
||||
userR := domain.User{
|
||||
FirstName: registerReq.FirstName,
|
||||
LastName: registerReq.LastName,
|
||||
UserName: registerReq.UserName,
|
||||
Email: registerReq.Email,
|
||||
PhoneNumber: registerReq.PhoneNumber,
|
||||
Password: hashedPassword,
|
||||
Role: domain.RoleStudent,
|
||||
EmailVerified: false, // verification pending via OTP
|
||||
PhoneVerified: false,
|
||||
EducationLevel: registerReq.EducationLevel,
|
||||
Age: registerReq.Age,
|
||||
Country: registerReq.Country,
|
||||
Region: registerReq.Region,
|
||||
Status: domain.UserStatusPending,
|
||||
ProfileCompleted: false,
|
||||
PreferredLanguage: registerReq.PreferredLanguage,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
var sentTo string
|
||||
// var provider domain.Provid
|
||||
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
||||
sentTo = registerReq.Email
|
||||
} else {
|
||||
sentTo = registerReq.PhoneNumber
|
||||
}
|
||||
//
|
||||
otp, err := s.otpStore.GetOtp(
|
||||
ctx, sentTo,
|
||||
domain.OtpRegister, registerReq.OtpMedium)
|
||||
if err != nil {
|
||||
|
||||
// Send OTP to the user (email/SMS)
|
||||
if err := s.SendOtp(ctx, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
// verify otp
|
||||
if otp.Used {
|
||||
return domain.User{}, domain.ErrOtpAlreadyUsed
|
||||
}
|
||||
if time.Now().After(otp.ExpiresAt) {
|
||||
return domain.User{}, domain.ErrOtpExpired
|
||||
}
|
||||
if otp.Otp != registerReq.Otp {
|
||||
return domain.User{}, domain.ErrInvalidOtp
|
||||
}
|
||||
|
||||
hashedPassword, err := hashPassword(registerReq.Password)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
userR := domain.User{
|
||||
FirstName: registerReq.FirstName,
|
||||
LastName: registerReq.LastName,
|
||||
Email: registerReq.Email,
|
||||
PhoneNumber: registerReq.PhoneNumber,
|
||||
Password: hashedPassword,
|
||||
Role: domain.RoleStudent,
|
||||
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
|
||||
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
|
||||
OrganizationID: registerReq.OrganizationID,
|
||||
}
|
||||
// create the user and mark otp as used
|
||||
user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
|
||||
// Create the user (no OTP validation yet)
|
||||
user, err := s.userStore.CreateUserWithoutOtp(ctx, userR)
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider, companyID domain.ValidInt64) error {
|
||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
|
||||
var err error
|
||||
// check if user exists
|
||||
switch medium {
|
||||
case domain.OtpMediumEmail:
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "")
|
||||
case domain.OtpMediumSms:
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
|
||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -27,37 +27,28 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
|
|||
}
|
||||
|
||||
func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error {
|
||||
var sentTo string
|
||||
if resetReq.OtpMedium == domain.OtpMediumEmail {
|
||||
sentTo = resetReq.Email
|
||||
} else {
|
||||
sentTo = resetReq.PhoneNumber
|
||||
}
|
||||
|
||||
otp, err := s.otpStore.GetOtp(
|
||||
ctx, sentTo,
|
||||
domain.OtpReset, resetReq.OtpMedium)
|
||||
otp, err := s.otpStore.GetOtp(ctx, resetReq.UserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//
|
||||
|
||||
user, err := s.userStore.GetUserByUserName(ctx, resetReq.UserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if otp.Used {
|
||||
return domain.ErrOtpAlreadyUsed
|
||||
}
|
||||
if time.Now().After(otp.ExpiresAt) {
|
||||
return domain.ErrOtpExpired
|
||||
}
|
||||
if otp.Otp != resetReq.Otp {
|
||||
if otp.Otp != resetReq.OtpCode {
|
||||
return domain.ErrInvalidOtp
|
||||
}
|
||||
// hash password
|
||||
// hashedPassword, err := hashPassword(resetReq.Password)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// reset pass and mark otp as used
|
||||
|
||||
err = s.userStore.UpdatePassword(ctx, resetReq.Password, resetReq.Email, resetReq.PhoneNumber, resetReq.OrganizationID, time.Now())
|
||||
err = s.userStore.UpdatePassword(ctx, resetReq.Password, user.Email, user.PhoneNumber, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,41 +3,54 @@ package user
|
|||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"context"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *int64, companyID *string) ([]domain.User, error) {
|
||||
func (s *Service) IsUserPending(ctx context.Context, userName string) (bool, error) {
|
||||
return s.userStore.IsUserPending(ctx, userName)
|
||||
}
|
||||
|
||||
func (s *Service) IsUserNameUnique(ctx context.Context, userName string) (bool, error) {
|
||||
return s.userStore.IsUserNameUnique(ctx, userName)
|
||||
}
|
||||
|
||||
func (s *Service) GetUserByUserName(
|
||||
ctx context.Context,
|
||||
userName string,
|
||||
) (domain.User, error) {
|
||||
return s.userStore.GetUserByUserName(ctx, userName)
|
||||
}
|
||||
|
||||
func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *int64) ([]domain.User, error) {
|
||||
// Search user
|
||||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, role, companyID)
|
||||
var roleStr *string
|
||||
if role != nil {
|
||||
tmp := strconv.FormatInt(*role, 10)
|
||||
roleStr = &tmp
|
||||
}
|
||||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, roleStr)
|
||||
|
||||
}
|
||||
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||
// update user
|
||||
|
||||
func (s *Service) UpdateUser(ctx context.Context, req domain.UpdateUserReq) error {
|
||||
newUser := domain.User{
|
||||
ID: user.UserID,
|
||||
FirstName: user.FirstName.Value,
|
||||
LastName: user.LastName.Value,
|
||||
NickName: user.NickName.Value,
|
||||
Age: user.Age.Value,
|
||||
EducationLevel: user.EducationLevel.Value,
|
||||
Country: user.Country.Value,
|
||||
Region: user.Region.Value,
|
||||
Suspended: user.Suspended.Value,
|
||||
OrganizationID: user.OrganizationID,
|
||||
ID: req.UserID,
|
||||
FirstName: req.FirstName.Value,
|
||||
LastName: req.LastName.Value,
|
||||
UserName: req.UserName.Value,
|
||||
Age: req.Age.Value,
|
||||
EducationLevel: req.EducationLevel.Value,
|
||||
Country: req.Country.Value,
|
||||
Region: req.Region.Value,
|
||||
}
|
||||
|
||||
// Update user in the store
|
||||
return s.userStore.UpdateUser(ctx, newUser)
|
||||
|
||||
}
|
||||
|
||||
// func (s *Service) UpdateUserCompany(ctx context.Context, id int64, companyID int64) error {
|
||||
// func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
// // update user
|
||||
// return s.userStore.UpdateUserCompany(ctx, id, companyID)
|
||||
// return s.userStore.UpdateUserSuspend(ctx, id, status)
|
||||
// }
|
||||
|
||||
func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
|
||||
// update user
|
||||
return s.userStore.UpdateUserSuspend(ctx, id, status)
|
||||
}
|
||||
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||
return s.userStore.GetUserByID(ctx, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ type CreateAdminReq struct {
|
|||
Email string `json:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||
Password string `json:"password" example:"password123"`
|
||||
OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
// CreateAdmin godoc
|
||||
|
|
@ -34,7 +33,7 @@ type CreateAdminReq struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/admin [post]
|
||||
func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
||||
var OrganizationID domain.ValidInt64
|
||||
// var OrganizationID domain.ValidInt64
|
||||
var req CreateAdminReq
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
|
|
@ -60,27 +59,27 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
if req.OrganizationID == nil {
|
||||
OrganizationID = domain.ValidInt64{
|
||||
Value: 0,
|
||||
Valid: false,
|
||||
}
|
||||
} else {
|
||||
// _, err := h.companySvc.GetCompanyByID(c.Context(), *req.OrganizationID)
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
|
||||
// zap.Int64("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Int64("company_id", *req.OrganizationID),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
|
||||
// if req.OrganizationID == nil {
|
||||
// OrganizationID = domain.ValidInt64{
|
||||
// Value: 0,
|
||||
// Valid: false,
|
||||
// }
|
||||
// } else {
|
||||
// // _, err := h.companySvc.GetCompanyByID(c.Context(), *req.OrganizationID)
|
||||
// // if err != nil {
|
||||
// // h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
|
||||
// // zap.Int64("status_code", fiber.StatusInternalServerError),
|
||||
// // zap.Int64("company_id", *req.OrganizationID),
|
||||
// // zap.Error(err),
|
||||
// // zap.Time("timestamp", time.Now()),
|
||||
// // )
|
||||
// // return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
|
||||
// // }
|
||||
// OrganizationID = domain.ValidInt64{
|
||||
// Value: *req.OrganizationID,
|
||||
// Valid: true,
|
||||
// }
|
||||
// }
|
||||
OrganizationID = domain.ValidInt64{
|
||||
Value: *req.OrganizationID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
user := domain.CreateUserReq{
|
||||
FirstName: req.FirstName,
|
||||
|
|
@ -89,7 +88,6 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
|||
PhoneNumber: req.PhoneNumber,
|
||||
Password: req.Password,
|
||||
Role: string(domain.RoleAdmin),
|
||||
OrganizationID: OrganizationID,
|
||||
}
|
||||
|
||||
newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
|
||||
|
|
@ -162,7 +160,6 @@ type AdminRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/admin [get]
|
||||
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||
|
||||
searchQuery := c.Query("query")
|
||||
searchString := domain.ValidString{
|
||||
Value: searchQuery,
|
||||
|
|
@ -172,38 +169,32 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
|||
createdBeforeQuery := c.Query("created_before")
|
||||
var createdBefore domain.ValidTime
|
||||
if createdBeforeQuery != "" {
|
||||
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
|
||||
parsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
|
||||
if err != nil {
|
||||
h.logger.Info("invalid start_time format", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
||||
}
|
||||
createdBefore = domain.ValidTime{
|
||||
Value: createdBeforeParsed,
|
||||
Valid: true,
|
||||
h.logger.Info("invalid created_before format", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
|
||||
}
|
||||
createdBefore = domain.ValidTime{Value: parsed, Valid: true}
|
||||
}
|
||||
|
||||
createdAfterQuery := c.Query("created_after")
|
||||
var createdAfter domain.ValidTime
|
||||
if createdAfterQuery != "" {
|
||||
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
|
||||
parsed, err := time.Parse(time.RFC3339, createdAfterQuery)
|
||||
if err != nil {
|
||||
h.logger.Info("invalid start_time format", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
||||
}
|
||||
createdAfter = domain.ValidTime{
|
||||
Value: createdAfterParsed,
|
||||
Valid: true,
|
||||
h.logger.Info("invalid created_after format", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
|
||||
}
|
||||
createdAfter = domain.ValidTime{Value: parsed, Valid: true}
|
||||
}
|
||||
|
||||
companyFilter := int64(c.QueryInt("company_id"))
|
||||
// companyID := int64(c.QueryInt("company_id"))
|
||||
filter := domain.UserFilter{
|
||||
Role: string(domain.RoleAdmin),
|
||||
OrganizationID: domain.ValidInt64{
|
||||
Value: companyFilter,
|
||||
Valid: companyFilter != 0,
|
||||
},
|
||||
// OrganizationID: domain.ValidInt64{
|
||||
// Value: companyID,
|
||||
// Valid: companyID != 0,
|
||||
// },
|
||||
Page: domain.ValidInt{
|
||||
Value: c.QueryInt("page", 1) - 1,
|
||||
Valid: true,
|
||||
|
|
@ -217,49 +208,44 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
|||
CreatedAfter: createdAfter,
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, filter)
|
||||
if !ok {
|
||||
if valErrs, ok := h.validator.Validate(c, filter); !ok {
|
||||
var errMsg string
|
||||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
for f, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", f, msg)
|
||||
}
|
||||
h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Any("validation_errors", valErrs),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
zap.Time("timestamp", time.Now()))
|
||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("failed to get admins from user service",
|
||||
h.mongoLoggerSvc.Error("failed to get admins",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Any("filter", filter),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Admins"+err.Error())
|
||||
zap.Time("timestamp", time.Now()))
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admins: "+err.Error())
|
||||
}
|
||||
|
||||
result := make([]AdminRes, len(admins))
|
||||
for index, admin := range admins {
|
||||
for i, admin := range admins {
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
|
||||
if err != nil {
|
||||
if err == authentication.ErrRefreshTokenNotFound {
|
||||
lastLogin = &admin.CreatedAt
|
||||
} else {
|
||||
h.mongoLoggerSvc.Error("failed to get last login for admin",
|
||||
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
||||
h.mongoLoggerSvc.Error("failed to get last login",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("admin_id", admin.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
|
||||
zap.Time("timestamp", time.Now()))
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get last login: "+err.Error())
|
||||
}
|
||||
if err == authentication.ErrRefreshTokenNotFound {
|
||||
lastLogin = &admin.CreatedAt
|
||||
}
|
||||
|
||||
result[index] = AdminRes{
|
||||
result[i] = AdminRes{
|
||||
ID: admin.ID,
|
||||
FirstName: admin.FirstName,
|
||||
LastName: admin.LastName,
|
||||
|
|
@ -269,9 +255,6 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
|||
EmailVerified: admin.EmailVerified,
|
||||
PhoneVerified: admin.PhoneVerified,
|
||||
CreatedAt: admin.CreatedAt,
|
||||
UpdatedAt: admin.UpdatedAt,
|
||||
SuspendedAt: admin.SuspendedAt,
|
||||
Suspended: admin.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
}
|
||||
}
|
||||
|
|
@ -299,38 +282,20 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/admin/{id} [get]
|
||||
func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
||||
userIDstr := c.Params("id")
|
||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
idStr := c.Params("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("invalid admin ID param",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.String("param", userIDstr),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid admin ID")
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), id)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("failed to fetch admin by ID",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("admin_id", userID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admin"+err.Error())
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admin: "+err.Error())
|
||||
}
|
||||
|
||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
||||
h.mongoLoggerSvc.Error("failed to get admin last login",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get last login: "+err.Error())
|
||||
}
|
||||
if err == authentication.ErrRefreshTokenNotFound {
|
||||
lastLogin = &user.CreatedAt
|
||||
|
|
@ -346,18 +311,9 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
|||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("admin retrieved successfully",
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Admin retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
|
|
@ -365,7 +321,6 @@ type updateAdminReq struct {
|
|||
FirstName string `json:"first_name" example:"John"`
|
||||
LastName string `json:"last_name" example:"Doe"`
|
||||
Suspended bool `json:"suspended" example:"false"`
|
||||
OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
// UpdateAdmin godoc
|
||||
|
|
@ -383,50 +338,25 @@ type updateAdminReq struct {
|
|||
func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
||||
var req updateAdminReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - invalid request body",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
if !ok {
|
||||
var errMsg string
|
||||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - validation errors",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Any("validation_errors", valErrs),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
AdminIDStr := c.Params("id")
|
||||
AdminID, err := strconv.ParseInt(AdminIDStr, 10, 64)
|
||||
adminIDStr := c.Params("id")
|
||||
adminID, err := strconv.ParseInt(adminIDStr, 10, 64)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("UpdateAdmin failed - invalid Admin ID param",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.String("admin_id_param", AdminIDStr),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid admin ID")
|
||||
}
|
||||
|
||||
var OrganizationID domain.ValidInt64
|
||||
if req.OrganizationID != nil {
|
||||
OrganizationID = domain.ValidInt64{
|
||||
Value: *req.OrganizationID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
// var orgID domain.ValidInt64
|
||||
// if req.OrganizationID != nil {
|
||||
// orgID = domain.ValidInt64{
|
||||
// Value: *req.OrganizationID,
|
||||
// Valid: true,
|
||||
// }
|
||||
// }
|
||||
|
||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||
UserID: AdminID,
|
||||
UserID: adminID,
|
||||
FirstName: domain.ValidString{
|
||||
Value: req.FirstName,
|
||||
Valid: req.FirstName != "",
|
||||
|
|
@ -435,46 +365,11 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
|||
Value: req.LastName,
|
||||
Valid: req.LastName != "",
|
||||
},
|
||||
Suspended: domain.ValidBool{
|
||||
Value: req.Suspended,
|
||||
Valid: true,
|
||||
},
|
||||
OrganizationID: OrganizationID,
|
||||
// OrganizationID: orgID,
|
||||
})
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("admin_id", AdminID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin: "+err.Error())
|
||||
}
|
||||
|
||||
// if req.OrganizationID != nil {
|
||||
// _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
|
||||
// ID: *req.OrganizationID,
|
||||
// AdminID: domain.ValidInt64{
|
||||
// Value: AdminID,
|
||||
// Valid: true,
|
||||
// },
|
||||
// })
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Error("UpdateAdmin failed to update company",
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Int64("admin_id", AdminID),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to update company:"+err.Error())
|
||||
// }
|
||||
// }
|
||||
|
||||
h.mongoLoggerSvc.Info("UpdateAdmin succeeded",
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Int64("admin_id", AdminID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil)
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Admin updated successfully", nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,41 +13,40 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// loginCustomerReq represents the request body for the LoginCustomer endpoint.
|
||||
type loginCustomerReq struct {
|
||||
Email string `json:"email" validate:"required_without=PhoneNumber" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
// loginUserReq represents the request body for the Loginuser endpoint.
|
||||
type loginUserReq struct {
|
||||
UserName string `json:"user_name" validate:"required" example:"johndoe"`
|
||||
Password string `json:"password" validate:"required" example:"password123"`
|
||||
}
|
||||
|
||||
// loginCustomerRes represents the response body for the LoginCustomer endpoint.
|
||||
type loginCustomerRes struct {
|
||||
// loginUserRes represents the response body for the Loginuser endpoint.
|
||||
type loginUserRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// LoginCustomer godoc
|
||||
// @Summary Login customer
|
||||
// @Description Login customer
|
||||
// Loginuser godoc
|
||||
// @Summary Login user
|
||||
// @Description Login user
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param login body loginCustomerReq true "Login customer"
|
||||
// @Success 200 {object} loginCustomerRes
|
||||
// @Param login body loginUserReq true "Login user"
|
||||
// @Success 200 {object} loginUserRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/{tenant_slug}/customer-login [post]
|
||||
func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||
OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
||||
if !OrganizationID.Valid {
|
||||
h.BadRequestLogger().Error("invalid company id")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
}
|
||||
var req loginCustomerReq
|
||||
// @Router /api/v1/{tenant_slug}/user-login [post]
|
||||
func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
||||
// OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
||||
// if !OrganizationID.Valid {
|
||||
// h.BadRequestLogger().Error("invalid company id")
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
// }
|
||||
var req loginUserReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse LoginCustomer request",
|
||||
h.mongoLoggerSvc.Info("Failed to parse LoginUser request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -63,15 +62,14 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, OrganizationID)
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.UserName, req.Password)
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -79,8 +77,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
case errors.Is(err, authentication.ErrUserSuspended):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -96,21 +93,19 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
if successRes.Role != domain.RoleStudent {
|
||||
h.mongoLoggerSvc.Info("Login attempt: customer login of other role",
|
||||
h.mongoLoggerSvc.Info("Login attempt: user login of other role",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only customers are allowed to login ")
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only users are allowed to login ")
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(
|
||||
successRes.UserId,
|
||||
successRes.Role,
|
||||
successRes.CompanyID,
|
||||
h.jwtConfig.JwtAccessKey,
|
||||
h.jwtConfig.JwtAccessExpiry,
|
||||
)
|
||||
|
|
@ -124,7 +119,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
res := loginUserRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
Role: string(successRes.Role),
|
||||
|
|
@ -142,8 +137,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
|||
|
||||
// loginAdminReq represents the request body for the LoginAdmin endpoint.
|
||||
type loginAdminReq struct {
|
||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
UserName string `json:"user_name" validate:"required" example:"adminuser"`
|
||||
Password string `json:"password" validate:"required" example:"password123"`
|
||||
}
|
||||
|
||||
|
|
@ -155,8 +149,8 @@ type LoginAdminRes struct {
|
|||
}
|
||||
|
||||
// LoginAdmin godoc
|
||||
// @Summary Login customer
|
||||
// @Description Login customer
|
||||
// @Summary Login user
|
||||
// @Description Login user
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
|
@ -167,11 +161,6 @@ type LoginAdminRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/{tenant_slug}/admin-login [post]
|
||||
func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
||||
OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
||||
if !OrganizationID.Valid {
|
||||
h.BadRequestLogger().Error("invalid company id")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
}
|
||||
var req loginAdminReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse LoginAdmin request",
|
||||
|
|
@ -190,14 +179,13 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, OrganizationID)
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.UserName, req.Password)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -205,8 +193,7 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
|||
case errors.Is(err, authentication.ErrUserSuspended):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -222,18 +209,17 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
if successRes.Role == domain.RoleStudent || successRes.Role == domain.RoleInstructor {
|
||||
h.mongoLoggerSvc.Warn("Login attempt: admin login of customer",
|
||||
h.mongoLoggerSvc.Warn("Login attempt: admin login of user",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
|
|
@ -244,7 +230,7 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
res := loginUserRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
Role: string(successRes.Role),
|
||||
|
|
@ -291,14 +277,13 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, domain.ValidInt64{})
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.UserName, req.Password)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -306,8 +291,7 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
|||
case errors.Is(err, authentication.ErrUserSuspended):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
|
@ -326,15 +310,14 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
|||
h.mongoLoggerSvc.Warn("Login attempt: super-admin login of non-super-admin",
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("email", req.Email),
|
||||
zap.String("phone", req.PhoneNumber),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
|
|
@ -345,7 +328,7 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
res := loginUserRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
Role: string(successRes.Role),
|
||||
|
|
@ -373,14 +356,14 @@ type refreshToken struct {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param refresh body refreshToken true "tokens"
|
||||
// @Success 200 {object} loginCustomerRes
|
||||
// @Success 200 {object} loginUserRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/auth/refresh [post]
|
||||
func (h *Handler) RefreshToken(c *fiber.Ctx) error {
|
||||
|
||||
type loginCustomerRes struct {
|
||||
type loginUserRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -451,7 +434,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information:"+err.Error())
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.OrganizationID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to create new access token",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
|
|
@ -462,7 +445,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token:"+err.Error())
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
res := loginUserRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: req.RefreshToken,
|
||||
Role: string(user.Role),
|
||||
|
|
@ -482,22 +465,22 @@ type logoutReq struct {
|
|||
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
|
||||
}
|
||||
|
||||
// LogOutCustomer godoc
|
||||
// @Summary Logout customer
|
||||
// @Description Logout customer
|
||||
// LogOutuser godoc
|
||||
// @Summary Logout user
|
||||
// @Description Logout user
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param logout body logoutReq true "Logout customer"
|
||||
// @Param logout body logoutReq true "Logout user"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/auth/logout [post]
|
||||
func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
|
||||
func (h *Handler) LogOutuser(c *fiber.Ctx) error {
|
||||
var req logoutReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse LogOutCustomer request",
|
||||
h.mongoLoggerSvc.Info("Failed to parse LogOutuser request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -512,7 +495,7 @@ func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
|
|||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("LogOutCustomer validation failed",
|
||||
h.mongoLoggerSvc.Info("LogOutuser validation failed",
|
||||
zap.String("errMsg", errMsg),
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Any("validation_errors", valErrs),
|
||||
|
|
|
|||
|
|
@ -1,455 +0,0 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"Yimaru-Backend/internal/web_server/response"
|
||||
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CreateManagerReq 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"`
|
||||
}
|
||||
|
||||
// CreateManager godoc
|
||||
// @Summary Create Manager
|
||||
// @Description Create Manager
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param manger body CreateManagerReq true "Create manager"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/managers [post]
|
||||
// func (h *Handler) CreateManager(c *fiber.Ctx) error {
|
||||
|
||||
// // Get user_id from middleware
|
||||
|
||||
// var req CreateManagerReq
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// h.logger.Error("RegisterUser failed", "error", err)
|
||||
// h.mongoLoggerSvc.Info("CreateManager failed to create manager",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
// }
|
||||
// valErrs, ok := h.validator.Validate(c, req)
|
||||
// if !ok {
|
||||
// var errMsg string
|
||||
// for field, msg := range valErrs {
|
||||
// errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
// }
|
||||
// h.mongoLoggerSvc.Info("Failed to validate CreateManager",
|
||||
// zap.Any("request", req),
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.String("errMsg", errMsg),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
// }
|
||||
|
||||
// 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")
|
||||
// h.mongoLoggerSvc.Info("RegisterUser failed error: company id is required",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Company ID is required for super-admin")
|
||||
// }
|
||||
// companyID = domain.ValidInt64{
|
||||
// Value: *req.CompanyID,
|
||||
// Valid: true,
|
||||
// }
|
||||
// } else {
|
||||
// companyID = c.Locals("company_id").(domain.ValidInt64)
|
||||
// }
|
||||
|
||||
// user := domain.CreateUserReq{
|
||||
// FirstName: req.FirstName,
|
||||
// LastName: req.LastName,
|
||||
// Email: req.Email,
|
||||
// PhoneNumber: req.PhoneNumber,
|
||||
// Password: req.Password,
|
||||
// Role: string(domain.RoleBranchManager),
|
||||
// OrganizationID: companyID,
|
||||
// }
|
||||
// _, err := h.userSvc.CreateUser(c.Context(), user, true)
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Error("CreateManager failed to create manager",
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to create manager:"+err.Error())
|
||||
// }
|
||||
// return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil)
|
||||
|
||||
// }
|
||||
|
||||
type ManagersRes struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Role domain.Role `json:"role"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
}
|
||||
|
||||
// GetAllManagers godoc
|
||||
// @Summary Get all Managers
|
||||
// @Description Get all Managers
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param page_size query int false "Page size"
|
||||
// @Success 200 {object} ManagersRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/managers [get]
|
||||
// func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
|
||||
// role := c.Locals("role").(domain.Role)
|
||||
// companyId := c.Locals("company_id").(domain.ValidInt64)
|
||||
|
||||
// // Checking to make sure that admin user has a company id in the token
|
||||
// if role != domain.RoleSuperAdmin && !companyId.Valid {
|
||||
// h.mongoLoggerSvc.Error("Cannot get company ID from context",
|
||||
// zap.String("role", string(role)),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID from context")
|
||||
// }
|
||||
|
||||
// searchQuery := c.Query("query")
|
||||
// searchString := domain.ValidString{
|
||||
// Value: searchQuery,
|
||||
// Valid: searchQuery != "",
|
||||
// }
|
||||
|
||||
// createdBeforeQuery := c.Query("created_before")
|
||||
// var createdBefore domain.ValidTime
|
||||
// if createdBeforeQuery != "" {
|
||||
// createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Info("invalid created_before format",
|
||||
// zap.String("created_before", createdBeforeQuery),
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
|
||||
// }
|
||||
// createdBefore = domain.ValidTime{
|
||||
// Value: createdBeforeParsed,
|
||||
// Valid: true,
|
||||
// }
|
||||
// }
|
||||
|
||||
// createdAfterQuery := c.Query("created_after")
|
||||
// var createdAfter domain.ValidTime
|
||||
// if createdAfterQuery != "" {
|
||||
// createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Info("invalid created_after format",
|
||||
// zap.String("created_after", createdAfterQuery),
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
|
||||
// }
|
||||
// createdAfter = domain.ValidTime{
|
||||
// Value: createdAfterParsed,
|
||||
// Valid: true,
|
||||
// }
|
||||
// }
|
||||
|
||||
// filter := domain.UserFilter{
|
||||
// Role: string(domain.RoleBranchManager),
|
||||
// OrganizationID: companyId,
|
||||
// Page: domain.ValidInt{
|
||||
// Value: c.QueryInt("page", 1) - 1,
|
||||
// Valid: true,
|
||||
// },
|
||||
// PageSize: domain.ValidInt{
|
||||
// Value: c.QueryInt("page_size", 10),
|
||||
// Valid: true,
|
||||
// },
|
||||
// Query: searchString,
|
||||
// CreatedBefore: createdBefore,
|
||||
// CreatedAfter: createdAfter,
|
||||
// }
|
||||
// valErrs, ok := h.validator.Validate(c, filter)
|
||||
// if !ok {
|
||||
// var errMsg string
|
||||
// for field, msg := range valErrs {
|
||||
// errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
// }
|
||||
// h.mongoLoggerSvc.Info("Failed to validate get all filters",
|
||||
// zap.Any("filter", filter),
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.String("errMsg", errMsg),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
// }
|
||||
// managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
|
||||
// if err != nil {
|
||||
// h.logger.Error("GetAllManagers failed", "error", err)
|
||||
// h.mongoLoggerSvc.Error("GetAllManagers failed to get all managers",
|
||||
// zap.Any("filter", filter),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Managers"+err.Error())
|
||||
// }
|
||||
|
||||
// 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.mongoLoggerSvc.Error("Failed to get user last login",
|
||||
// zap.Int64("userID", manager.ID),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
|
||||
// }
|
||||
// }
|
||||
// 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.Value, int(total))
|
||||
|
||||
// }
|
||||
|
||||
// GetManagerByID godoc
|
||||
// @Summary Get manager by id
|
||||
// @Description Get a single manager by id
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} ManagersRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/managers/{id} [get]
|
||||
// func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
|
||||
// role := c.Locals("role").(domain.Role)
|
||||
// companyId := c.Locals("company_id").(domain.ValidInt64)
|
||||
// requestUserID := c.Locals("user_id").(int64)
|
||||
|
||||
// // Only Super Admin / Admin / Branch Manager can view this route
|
||||
// if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager {
|
||||
// h.mongoLoggerSvc.Warn("Attempt to access from unauthorized role",
|
||||
// zap.Int64("userID", requestUserID),
|
||||
// zap.String("role", string(role)),
|
||||
// zap.Int("status_code", fiber.StatusForbidden),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusForbidden, "This role cannot view this route")
|
||||
// }
|
||||
|
||||
// if role != domain.RoleSuperAdmin && !companyId.Valid {
|
||||
// h.mongoLoggerSvc.Error("Cannot get company ID in context",
|
||||
// zap.String("role", string(role)),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context")
|
||||
// }
|
||||
|
||||
// userIDstr := c.Params("id")
|
||||
// userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
||||
// if err != nil {
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid managers ID")
|
||||
// }
|
||||
|
||||
// user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Error("Failed to get manager by id",
|
||||
// zap.Int64("userID", userID),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to get managers:"+err.Error())
|
||||
// }
|
||||
|
||||
// // A Branch Manager can only fetch his own branch info
|
||||
// if role == domain.RoleBranchManager && user.ID != requestUserID {
|
||||
// h.mongoLoggerSvc.Warn("Attempt to access another branch manager info",
|
||||
// zap.String("userID", userIDstr),
|
||||
// zap.Int("status_code", fiber.StatusForbidden),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusForbidden, "User Access Not Allowed")
|
||||
// }
|
||||
|
||||
// // Check that only admin from company can view this route
|
||||
// if role != domain.RoleSuperAdmin && user.OrganizationID.Value != companyId.Value {
|
||||
// h.mongoLoggerSvc.Warn("Attempt to access info from another company",
|
||||
// zap.String("userID", userIDstr),
|
||||
// zap.Int("status_code", fiber.StatusForbidden),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusForbidden, "Cannot access another company information")
|
||||
// }
|
||||
|
||||
// lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||
// if err != nil {
|
||||
// if err != authentication.ErrRefreshTokenNotFound {
|
||||
// h.mongoLoggerSvc.Error("Failed to get user last login",
|
||||
// zap.Int64("userID", userID),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
|
||||
// }
|
||||
|
||||
// lastLogin = &user.CreatedAt
|
||||
// }
|
||||
|
||||
// res := ManagersRes{
|
||||
// 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, "User retrieved successfully", res, nil)
|
||||
// }
|
||||
|
||||
type updateManagerReq struct {
|
||||
FirstName string `json:"first_name" example:"John"`
|
||||
LastName string `json:"last_name" example:"Doe"`
|
||||
Suspended bool `json:"suspended" example:"false"`
|
||||
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
// UpdateManagers godoc
|
||||
// @Summary Update Managers
|
||||
// @Description Update Managers
|
||||
// @Tags manager
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Managers body updateManagerReq true "Update Managers"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/managers/{id} [put]
|
||||
func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
|
||||
|
||||
var req updateManagerReq
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
|
||||
if !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
ManagersIdStr := c.Params("id")
|
||||
ManagersId, err := strconv.ParseInt(ManagersIdStr, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid Managers ID", nil, nil)
|
||||
}
|
||||
var companyID domain.ValidInt64
|
||||
role := c.Locals("role").(domain.Role)
|
||||
if req.CompanyID != nil {
|
||||
if role != domain.RoleSuperAdmin {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusUnauthorized, "This user role cannot modify company ID", nil, nil)
|
||||
}
|
||||
companyID = domain.ValidInt64{
|
||||
Value: *req.CompanyID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||
UserID: ManagersId,
|
||||
FirstName: domain.ValidString{
|
||||
Value: req.FirstName,
|
||||
Valid: req.FirstName != "",
|
||||
},
|
||||
LastName: domain.ValidString{
|
||||
Value: req.LastName,
|
||||
Valid: req.LastName != "",
|
||||
},
|
||||
Suspended: domain.ValidBool{
|
||||
Value: req.Suspended,
|
||||
Valid: true,
|
||||
},
|
||||
OrganizationID: companyID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
h.logger.Error("UpdateManagers failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update Managers", nil, nil)
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil)
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"Yimaru-Backend/internal/web_server/ws"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
|
@ -449,3 +450,77 @@ func (h *Handler) GetAllNotifications(c *fiber.Ctx) error {
|
|||
})
|
||||
|
||||
}
|
||||
|
||||
type SendSingleAfroSMSReq struct {
|
||||
Recipient string `json:"recipient" validate:"required" example:"+251912345678"`
|
||||
Message string `json:"message" validate:"required" example:"Hello world"`
|
||||
}
|
||||
|
||||
// SendSingleAfroSMS godoc
|
||||
// @Summary Send single SMS via AfroMessage
|
||||
// @Description Sends an SMS message to a single phone number using AfroMessage
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param sendSMS body SendSingleAfroSMSReq true "Send SMS request"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/sendSMS [post]
|
||||
func (h *Handler) SendSingleAfroSMS(c *fiber.Ctx) error {
|
||||
var req SendSingleAfroSMSReq
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse SendSingleAfroSMS request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to send SMS",
|
||||
Error: "Invalid request body: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Validate request
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
var errMsg string
|
||||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to send SMS",
|
||||
Error: errMsg,
|
||||
})
|
||||
}
|
||||
|
||||
// Send SMS via service
|
||||
if err := h.notificationSvc.SendAfroMessageSMSTemp(
|
||||
c.Context(),
|
||||
req.Recipient,
|
||||
req.Message,
|
||||
nil,
|
||||
); err != nil {
|
||||
|
||||
h.mongoLoggerSvc.Error("Failed to send AfroMessage SMS",
|
||||
zap.String("phone_number", req.Recipient),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to send SMS",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "SMS sent successfully",
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Data: req,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id")
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
_, err := h.userSvc.GetUserByID(c.Context(), userID)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to get user",
|
||||
zap.Int64("userID", userID),
|
||||
|
|
@ -72,15 +72,15 @@ func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
|
||||
}
|
||||
|
||||
if !user.OrganizationID.Valid || user.OrganizationID.Value != companyID.Value {
|
||||
h.mongoLoggerSvc.Warn("User attempt to login to different company",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
|
||||
}
|
||||
// if !user.OrganizationID.Valid || user.OrganizationID.Value != companyID.Value {
|
||||
// h.mongoLoggerSvc.Warn("User attempt to login to different company",
|
||||
// zap.Int64("userID", userID),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
|
||||
// }
|
||||
|
||||
// referrals, err := h.referralSvc.GetReferralCodesByUser(c.Context(), user.ID)
|
||||
|
||||
|
|
|
|||
|
|
@ -333,9 +333,8 @@ func (h *Handler) GetTransactionApproverByID(c *fiber.Ctx) error {
|
|||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
// SuspendedAt: user.SuspendedAt,
|
||||
// Suspended: user.Suspended,
|
||||
LastLogin: *lastLogin,
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +368,7 @@ type updateTransactionApproverReq struct {
|
|||
func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
||||
var req updateTransactionApproverReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - invalid request body",
|
||||
h.mongoLoggerSvc.Error("UpdateTransactionApprover failed - invalid request body",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -377,13 +376,12 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||
}
|
||||
|
||||
valErrs, ok := h.validator.Validate(c, req)
|
||||
if !ok {
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
var errMsg string
|
||||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - validation errors",
|
||||
h.mongoLoggerSvc.Error("UpdateTransactionApprover failed - validation errors",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Any("validation_errors", valErrs),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
|
|
@ -391,20 +389,20 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
}
|
||||
|
||||
ApproverIDStr := c.Params("id")
|
||||
ApproverID, err := strconv.ParseInt(ApproverIDStr, 10, 64)
|
||||
approverIDStr := c.Params("id")
|
||||
approverID, err := strconv.ParseInt(approverIDStr, 10, 64)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("UpdateAdmin failed - invalid Admin ID param",
|
||||
h.mongoLoggerSvc.Info("UpdateTransactionApprover failed - invalid approver ID",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.String("admin_id_param", ApproverIDStr),
|
||||
zap.String("approver_id_param", approverIDStr),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid approver ID")
|
||||
}
|
||||
|
||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||
UserID: ApproverID,
|
||||
updateReq := domain.UpdateUserReq{
|
||||
UserID: approverID,
|
||||
FirstName: domain.ValidString{
|
||||
Value: req.FirstName,
|
||||
Valid: req.FirstName != "",
|
||||
|
|
@ -413,26 +411,25 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
|||
Value: req.LastName,
|
||||
Valid: req.LastName != "",
|
||||
},
|
||||
Suspended: domain.ValidBool{
|
||||
Value: req.Suspended,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
err = h.userSvc.UpdateUser(c.Context(), updateReq)
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error",
|
||||
h.mongoLoggerSvc.Error("UpdateTransactionApprover failed - user service error",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Int64("admin_id", ApproverID),
|
||||
zap.Int64("approver_id", approverID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin:"+err.Error())
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update approver: "+err.Error())
|
||||
}
|
||||
|
||||
h.mongoLoggerSvc.Info("UpdateAdmin succeeded",
|
||||
h.mongoLoggerSvc.Info("UpdateTransactionApprover succeeded",
|
||||
zap.Int("status_code", fiber.StatusOK),
|
||||
zap.Int64("admin_id", ApproverID),
|
||||
zap.Int64("approver_id", approverID),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Managers updated successfully", nil, nil)
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Transaction approver updated successfully", nil, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -28,7 +28,7 @@ type JwtConfig struct {
|
|||
JwtAccessExpiry int
|
||||
}
|
||||
|
||||
func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) {
|
||||
func CreateJwt(userId int64, Role domain.Role, key string, expiry int) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: "yimaru.com",
|
||||
|
|
@ -39,10 +39,10 @@ func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key
|
|||
},
|
||||
UserId: userId,
|
||||
Role: Role,
|
||||
CompanyID: domain.NullJwtInt64{
|
||||
Value: CompanyID.Value,
|
||||
Valid: CompanyID.Valid,
|
||||
},
|
||||
// CompanyID: domain.NullJwtInt64{
|
||||
// Value: CompanyID.Value,
|
||||
// Valid: CompanyID.Valid,
|
||||
// },
|
||||
})
|
||||
jwtToken, err := token.SignedString([]byte(key))
|
||||
return jwtToken, err
|
||||
|
|
|
|||
|
|
@ -80,11 +80,11 @@ func (a *App) initAppRoutes() {
|
|||
})
|
||||
|
||||
// Auth Routes
|
||||
tenant.Post("/auth/customer-login", h.LoginCustomer)
|
||||
tenant.Post("/auth/customer-login", h.LoginUser)
|
||||
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
||||
groupV1.Post("/auth/super-login", h.LoginSuper)
|
||||
groupV1.Post("/auth/refresh", h.RefreshToken)
|
||||
groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
|
||||
groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutuser)
|
||||
groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok {
|
||||
|
|
@ -122,8 +122,12 @@ func (a *App) initAppRoutes() {
|
|||
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
||||
|
||||
// User Routes
|
||||
groupV1.Get("/user/:user_name/is-unique", h.CheckUserNameUnique)
|
||||
groupV1.Get("/user/:user_name/is-pending", h.CheckUserPending)
|
||||
groupV1.Post("/user/resetPassword", h.ResetPassword)
|
||||
groupV1.Post("/user/sendResetCode", h.SendResetCode)
|
||||
groupV1.Post("/user/verify-otp", h.VerifyOtp)
|
||||
groupV1.Post("/user/resend-otp", h.ResendOtp)
|
||||
|
||||
tenant.Post("/user/resetPassword", h.ResetTenantPassword)
|
||||
tenant.Post("/user/sendResetCode", h.SendTenantResetCode)
|
||||
|
|
@ -133,10 +137,9 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
|
||||
|
||||
tenant.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile)
|
||||
tenant.Get("/user/user-profile", a.authMiddleware, h.GetUserProfile)
|
||||
|
||||
groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
|
||||
groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
|
||||
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
|
||||
groupV1.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
|
||||
|
||||
|
|
@ -150,7 +153,6 @@ func (a *App) initAppRoutes() {
|
|||
// groupV1.Post("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateTransactionApprover)
|
||||
// groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover)
|
||||
|
||||
|
||||
//mongoDB logs
|
||||
groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background()))
|
||||
|
||||
|
|
@ -160,6 +162,7 @@ func (a *App) initAppRoutes() {
|
|||
// groupV1.Put("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.UpdateTransactionVerified)
|
||||
|
||||
// Notification Routes
|
||||
groupV1.Post("/sendSMS", h.SendSingleAfroSMS)
|
||||
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
||||
groupV1.Get("/notifications", a.authMiddleware, h.GetUserNotification)
|
||||
groupV1.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
|
||||
|
|
|
|||
108
makefile copy
108
makefile copy
|
|
@ -1,108 +0,0 @@
|
|||
include .env
|
||||
.PHONY: test
|
||||
test:
|
||||
@docker compose up -d test
|
||||
@docker compose exec test go test ./...
|
||||
@docker compose stop test
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
@mkdir -p coverage
|
||||
@docker compose up -d test
|
||||
@docker compose exec test sh -c "go test -coverprofile=coverage.out ./internal/... && go tool cover -func=coverage.out -o coverage/coverage.txt"
|
||||
@docker cp $(shell docker ps -q -f "name=yimaru-test-1"):/app/coverage ./ || true
|
||||
@docker compose stop test
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
@docker compose build app
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
@docker compose up
|
||||
|
||||
.PHONY: stop
|
||||
stop:
|
||||
@docker compose down
|
||||
|
||||
.PHONY: air
|
||||
air:
|
||||
@echo "Running air locally (not in Docker)"
|
||||
@air -c .air.toml
|
||||
.PHONY: migrations/new
|
||||
migrations/new:
|
||||
@echo 'Creating migration files for DB_URL'
|
||||
@migrate create -seq -ext=.sql -dir=./db/migrations $(name)
|
||||
|
||||
.PHONY: migrations/up
|
||||
migrations/up:
|
||||
@echo 'Running up migrations...'
|
||||
@docker compose up migrate
|
||||
|
||||
.PHONY: postgres
|
||||
postgres:
|
||||
@echo 'Running postgres db...'
|
||||
docker compose -f docker-compose.yml exec postgres psql -U root -d gh
|
||||
.PHONY: backup
|
||||
backup:
|
||||
@mkdir -p backup
|
||||
@docker exec -t yimaru-backend-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
|
||||
|
||||
restore:
|
||||
@echo "Restoring latest backup..."
|
||||
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
|
||||
echo "Restoring from $$latest_file"; \
|
||||
gunzip -c $$latest_file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
|
||||
restore_file:
|
||||
@echo "Restoring latest backup..."
|
||||
gunzip -c $(file) | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
|
||||
|
||||
.PHONY: seed_data
|
||||
seed_data:
|
||||
|
||||
@echo "Waiting for PostgreSQL to be ready..."
|
||||
@until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
|
||||
echo "PostgreSQL is not ready yet..."; \
|
||||
sleep 1; \
|
||||
done
|
||||
@for file in db/data/*.sql; do \
|
||||
echo "Seeding $$file..."; \
|
||||
cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
|
||||
done
|
||||
.PHONY: seed_dev_data
|
||||
seed_dev_data:
|
||||
@echo "Waiting for PostgreSQL to be ready..."
|
||||
@until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
|
||||
echo "PostgreSQL is not ready yet..."; \
|
||||
sleep 1; \
|
||||
done
|
||||
cat db/scripts/fix_autoincrement_desync.sql | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh;
|
||||
@for file in db/dev_data/*.sql; do \
|
||||
if [ -f "$$file" ]; then \
|
||||
echo "Seeding $$file..."; \
|
||||
cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
|
||||
fi \
|
||||
done
|
||||
postgres_log:
|
||||
docker logs yimaru-backend-postgres-1
|
||||
.PHONY: swagger
|
||||
swagger:
|
||||
@swag init -g cmd/main.go
|
||||
.PHONY: db-up
|
||||
logs:
|
||||
@mkdir -p logs
|
||||
db-up: | logs
|
||||
@mkdir -p logs
|
||||
@docker compose up -d postgres migrate mongo
|
||||
@docker logs yimaru-backend-postgres-1 > logs/postgres.log 2>&1 &
|
||||
.PHONY: db-down
|
||||
db-down:
|
||||
@docker compose down -v
|
||||
# @docker volume rm yimaru-backend_postgres_data
|
||||
.PHONY: sqlc-gen
|
||||
sqlc-gen:
|
||||
@sqlc generate
|
||||
app_log:
|
||||
@mkdir -p app_logs
|
||||
export_logs: | app_log
|
||||
@docker exec yimaru-mongo mongoexport --db=logdb --collection=applogs -u root -p secret --authenticationDatabase=admin --out - > app_logs/log_`date +%Y-%m-%d"_"%H_%M_%S`.json
|
||||
Loading…
Reference in New Issue
Block a user