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,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
password,
|
password,
|
||||||
|
|
@ -128,7 +128,7 @@ VALUES
|
||||||
ON CONFLICT (id) DO UPDATE
|
ON CONFLICT (id) DO UPDATE
|
||||||
SET first_name = EXCLUDED.first_name,
|
SET first_name = EXCLUDED.first_name,
|
||||||
last_name = EXCLUDED.last_name,
|
last_name = EXCLUDED.last_name,
|
||||||
nick_name = EXCLUDED.nick_name,
|
user_name = EXCLUDED.user_name,
|
||||||
email = EXCLUDED.email,
|
email = EXCLUDED.email,
|
||||||
phone_number = EXCLUDED.phone_number,
|
phone_number = EXCLUDED.phone_number,
|
||||||
password = EXCLUDED.password,
|
password = EXCLUDED.password,
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,6 @@ SELECT setval(
|
||||||
)
|
)
|
||||||
FROM users;
|
FROM users;
|
||||||
|
|
||||||
SELECT setval(
|
|
||||||
pg_get_serial_sequence('organizations', 'id'),
|
|
||||||
COALESCE(MAX(id), 1)
|
|
||||||
)
|
|
||||||
FROM organizations;
|
|
||||||
|
|
||||||
SELECT setval(
|
SELECT setval(
|
||||||
pg_get_serial_sequence('courses', 'id'),
|
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 course_modules;
|
||||||
DROP TABLE IF EXISTS courses;
|
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
|
-- Authentication & Security
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
first_name VARCHAR(255) NOT NULL,
|
first_name VARCHAR(255) NOT NULL,
|
||||||
last_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,
|
email VARCHAR(255) UNIQUE,
|
||||||
phone_number VARCHAR(20) 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,
|
password BYTEA NOT NULL,
|
||||||
age INT,
|
age INT,
|
||||||
education_level VARCHAR(100),
|
education_level VARCHAR(100),
|
||||||
|
|
@ -13,16 +14,19 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
region VARCHAR(100),
|
region VARCHAR(100),
|
||||||
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
status VARCHAR(50) NOT NULL, -- PENDING, ACTIVE, SUSPENDED, DEACTIVATED
|
||||||
suspended_at TIMESTAMPTZ,
|
last_login TIMESTAMPTZ,
|
||||||
organization_id BIGINT,
|
profile_completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
profile_picture_url TEXT,
|
||||||
|
preferred_language VARCHAR(50),
|
||||||
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ,
|
updated_at TIMESTAMPTZ,
|
||||||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL),
|
|
||||||
UNIQUE (email, organization_id),
|
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||||
UNIQUE (phone_number, organization_id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE refresh_tokens (
|
CREATE TABLE refresh_tokens (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
@ -34,9 +38,10 @@ CREATE TABLE refresh_tokens (
|
||||||
|
|
||||||
CREATE TABLE otps (
|
CREATE TABLE otps (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_name VARCHAR(100) NOT NULL,
|
||||||
sent_to VARCHAR(255) NOT NULL,
|
sent_to VARCHAR(255) NOT NULL,
|
||||||
medium VARCHAR(50) NOT NULL, -- email, sms
|
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,
|
otp VARCHAR(10) NOT NULL,
|
||||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
used_at TIMESTAMPTZ,
|
used_at TIMESTAMPTZ,
|
||||||
|
|
@ -44,19 +49,8 @@ CREATE TABLE otps (
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
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 (
|
CREATE TABLE courses (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
organization_id BIGINT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
||||||
instructor_id BIGINT NOT NULL REFERENCES users(id),
|
instructor_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
|
|
@ -170,15 +164,6 @@ CREATE TABLE global_settings (
|
||||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
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 (
|
CREATE TABLE IF NOT EXISTS reported_issues (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
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
|
-- name: CreateOtp :exec
|
||||||
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||||
VALUES ($1, $2, $3, $4, FALSE, $5, $6);
|
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7);
|
||||||
|
|
||||||
-- name: GetOtp :one
|
-- 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
|
FROM otps
|
||||||
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
WHERE user_name = $1
|
||||||
ORDER BY created_at DESC LIMIT 1;
|
ORDER BY created_at DESC LIMIT 1;
|
||||||
|
|
||||||
-- name: MarkOtpAsUsed :exec
|
-- name: MarkOtpAsUsed :exec
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,61 @@
|
||||||
|
-- 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
|
-- name: CreateUser :one
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
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
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
$1,
|
|
||||||
$2,
|
|
||||||
$3,
|
|
||||||
$4,
|
|
||||||
$5,
|
|
||||||
$6,
|
|
||||||
$7,
|
|
||||||
$8,
|
|
||||||
$9,
|
|
||||||
$10,
|
|
||||||
$11,
|
|
||||||
$12,
|
|
||||||
$13,
|
|
||||||
$14,
|
|
||||||
$15,
|
|
||||||
$16,
|
|
||||||
$17,
|
|
||||||
$18
|
|
||||||
)
|
|
||||||
RETURNING id,
|
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
|
email,
|
||||||
|
phone_number,
|
||||||
|
role,
|
||||||
|
password,
|
||||||
|
age,
|
||||||
|
education_level,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
email_verified,
|
||||||
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$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,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -52,22 +65,24 @@ RETURNING id,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at;
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id;
|
|
||||||
-- name: GetUserByID :one
|
-- name: GetUserByID :one
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: GetAllUsers :many
|
-- name: GetAllUsers :many
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) OVER () AS total_count,
|
COUNT(*) OVER () AS total_count,
|
||||||
id,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -77,52 +92,44 @@ SELECT
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (
|
WHERE (
|
||||||
role = $1
|
role = $1 OR $1 IS NULL
|
||||||
OR $1 IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
organization_id = $2
|
|
||||||
OR $2 IS NULL
|
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
first_name ILIKE '%' || sqlc.narg('query') || '%'
|
first_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
OR last_name ILIKE '%' || sqlc.narg('query') || '%'
|
OR last_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
OR sqlc.narg('query') IS NULL
|
OR email ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
|
OR sqlc.narg('query') IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
created_at > sqlc.narg('created_before')
|
created_at >= sqlc.narg('created_after')
|
||||||
OR sqlc.narg('created_before') IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
created_at < sqlc.narg('created_after')
|
|
||||||
OR sqlc.narg('created_after') IS NULL
|
OR sqlc.narg('created_after') IS NULL
|
||||||
)
|
)
|
||||||
|
AND (
|
||||||
|
created_at <= sqlc.narg('created_before')
|
||||||
|
OR sqlc.narg('created_before') IS NULL
|
||||||
|
)
|
||||||
LIMIT sqlc.narg('limit')
|
LIMIT sqlc.narg('limit')
|
||||||
OFFSET sqlc.narg('offset');
|
OFFSET sqlc.narg('offset');
|
||||||
|
|
||||||
-- name: GetTotalUsers :one
|
-- name: GetTotalUsers :one
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM users
|
FROM users
|
||||||
wHERE (
|
WHERE (role = $1 OR $1 IS NULL);
|
||||||
role = $1
|
|
||||||
OR $1 IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
organization_id = $2
|
|
||||||
OR $2 IS NULL
|
|
||||||
);
|
|
||||||
-- name: SearchUserByNameOrPhone :many
|
-- name: SearchUserByNameOrPhone :many
|
||||||
SELECT id,
|
SELECT
|
||||||
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -132,98 +139,113 @@ SELECT id,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (
|
WHERE (
|
||||||
organization_id = sqlc.narg('organization_id')
|
|
||||||
OR sqlc.narg('organization_id') IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
first_name ILIKE '%' || $1 || '%'
|
first_name ILIKE '%' || $1 || '%'
|
||||||
OR last_name ILIKE '%' || $1 || '%'
|
OR last_name ILIKE '%' || $1 || '%'
|
||||||
OR phone_number LIKE '%' || $1 || '%'
|
OR phone_number ILIKE '%' || $1 || '%'
|
||||||
|
OR email ILIKE '%' || $1 || '%'
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
role = sqlc.narg('role')
|
role = sqlc.narg('role')
|
||||||
OR sqlc.narg('role') IS NULL
|
OR sqlc.narg('role') IS NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- name: UpdateUser :exec
|
-- name: UpdateUser :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET first_name = $1,
|
SET
|
||||||
last_name = $2,
|
first_name = $1,
|
||||||
suspended = $3,
|
last_name = $2,
|
||||||
|
status = $3,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $4;
|
WHERE id = $4;
|
||||||
-- name: UpdateUserOrganization :exec
|
|
||||||
UPDATE users
|
|
||||||
SET organization_id = $1
|
|
||||||
WHERE id = $2;
|
|
||||||
-- name: DeleteUser :exec
|
-- name: DeleteUser :exec
|
||||||
DELETE FROM users
|
DELETE FROM users
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: CheckPhoneEmailExist :one
|
-- name: CheckPhoneEmailExist :one
|
||||||
SELECT EXISTS (
|
SELECT
|
||||||
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM users
|
FROM users u1
|
||||||
WHERE users.phone_number = $1
|
WHERE u1.phone_number = $1
|
||||||
AND users.phone_number IS NOT NULL
|
|
||||||
AND users.organization_id = $2
|
|
||||||
) AS phone_exists,
|
) AS phone_exists,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM users
|
FROM users u2
|
||||||
WHERE users.email = $3
|
WHERE u2.email = $2
|
||||||
AND users.email IS NOT NULL
|
|
||||||
AND users.organization_id = $2
|
|
||||||
) AS email_exists;
|
) AS email_exists;
|
||||||
|
|
||||||
|
-- name: GetUserByUserName :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 user_name = $1 AND $1 IS NOT NULL
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: GetUserByEmailPhone :one
|
-- name: GetUserByEmailPhone :one
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
password, -- added this line
|
password,
|
||||||
age,
|
age,
|
||||||
education_level,
|
education_level,
|
||||||
country,
|
country,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
last_login,
|
||||||
|
profile_picture_url,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE organization_id = $3
|
WHERE (email = $1 AND $1 IS NOT NULL)
|
||||||
AND (
|
|
||||||
(email = $1 AND $1 IS NOT NULL)
|
|
||||||
OR (phone_number = $2 AND $2 IS NOT NULL)
|
OR (phone_number = $2 AND $2 IS NOT NULL)
|
||||||
)
|
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: UpdatePassword :exec
|
-- name: UpdatePassword :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET password = $1,
|
SET
|
||||||
updated_at = $4
|
password = $1,
|
||||||
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,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
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": {
|
"/api/v1/super-login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Login super-admin",
|
"description": "Login super-admin",
|
||||||
|
|
@ -761,7 +709,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -851,7 +799,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -875,9 +823,9 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/user/suspend": {
|
"/api/v1/user/{user_name}/is-unique": {
|
||||||
"post": {
|
"get": {
|
||||||
"description": "Suspend or unsuspend a user",
|
"description": "Returns whether the specified user_name is available (unique)",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -887,35 +835,33 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"summary": "Suspend or unsuspend a user",
|
"summary": "Check if user_name is unique",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Suspend or unsuspend a user",
|
"type": "string",
|
||||||
"name": "updateUserSuspend",
|
"description": "User Name",
|
||||||
"in": "body",
|
"name": "user_name",
|
||||||
"required": true,
|
"in": "path",
|
||||||
"schema": {
|
"required": true
|
||||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request",
|
"description": "Bad Request",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error",
|
"description": "Internal Server Error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1171,7 +1117,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"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": {
|
"/api/v1/{tenant_slug}/user/sendRegisterCode": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Send register code",
|
"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": {
|
"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": {
|
"domain.Pagination": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -1475,6 +1559,135 @@ const docTemplate = `{
|
||||||
"RoleSupport"
|
"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": {
|
"handlers.AdminProfileRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"handlers.ResetCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"handlers.loginAdminReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -1870,20 +1976,17 @@ const docTemplate = `{
|
||||||
"handlers.loginCustomerReq": {
|
"handlers.loginCustomerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"password"
|
"password",
|
||||||
|
"user_name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "john.doe@example.com"
|
|
||||||
},
|
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "password123"
|
"example": "password123"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"user_name": {
|
||||||
"type": "string",
|
"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": {
|
"response.APIResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"/api/v1/super-login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Login super-admin",
|
"description": "Login super-admin",
|
||||||
|
|
@ -753,7 +701,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -843,7 +791,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UserProfileRes"
|
"$ref": "#/definitions/domain.UserProfileResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -867,9 +815,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/user/suspend": {
|
"/api/v1/user/{user_name}/is-unique": {
|
||||||
"post": {
|
"get": {
|
||||||
"description": "Suspend or unsuspend a user",
|
"description": "Returns whether the specified user_name is available (unique)",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -879,35 +827,33 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"summary": "Suspend or unsuspend a user",
|
"summary": "Check if user_name is unique",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Suspend or unsuspend a user",
|
"type": "string",
|
||||||
"name": "updateUserSuspend",
|
"description": "User Name",
|
||||||
"in": "body",
|
"name": "user_name",
|
||||||
"required": true,
|
"in": "path",
|
||||||
"schema": {
|
"required": true
|
||||||
"$ref": "#/definitions/handlers.UpdateUserSuspendReq"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.UpdateUserSuspendRes"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request",
|
"description": "Bad Request",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error",
|
"description": "Internal Server Error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1163,7 +1109,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"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": {
|
"/api/v1/{tenant_slug}/user/sendRegisterCode": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Send register code",
|
"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": {
|
"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": {
|
"domain.Pagination": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -1467,6 +1551,135 @@
|
||||||
"RoleSupport"
|
"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": {
|
"handlers.AdminProfileRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"handlers.ResetCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"handlers.loginAdminReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -1862,20 +1968,17 @@
|
||||||
"handlers.loginCustomerReq": {
|
"handlers.loginCustomerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"password"
|
"password",
|
||||||
|
"user_name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "john.doe@example.com"
|
|
||||||
},
|
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "password123"
|
"example": "password123"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"user_name": {
|
||||||
"type": "string",
|
"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": {
|
"response.APIResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,22 @@ definitions:
|
||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/domain.Pagination'
|
$ref: '#/definitions/domain.Pagination'
|
||||||
type: object
|
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:
|
domain.Pagination:
|
||||||
properties:
|
properties:
|
||||||
current_page:
|
current_page:
|
||||||
|
|
@ -48,6 +64,50 @@ definitions:
|
||||||
total_pages:
|
total_pages:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
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:
|
domain.Role:
|
||||||
enum:
|
enum:
|
||||||
- super_admin
|
- super_admin
|
||||||
|
|
@ -62,6 +122,93 @@ definitions:
|
||||||
- RoleStudent
|
- RoleStudent
|
||||||
- RoleInstructor
|
- RoleInstructor
|
||||||
- RoleSupport
|
- 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:
|
handlers.AdminProfileRes:
|
||||||
properties:
|
properties:
|
||||||
created_at:
|
created_at:
|
||||||
|
|
@ -206,30 +353,6 @@ definitions:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
handlers.ResetCodeReq:
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
|
|
@ -265,55 +388,6 @@ definitions:
|
||||||
role:
|
role:
|
||||||
$ref: '#/definitions/domain.Role'
|
$ref: '#/definitions/domain.Role'
|
||||||
type: object
|
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:
|
handlers.loginAdminReq:
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
|
|
@ -330,17 +404,15 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
handlers.loginCustomerReq:
|
handlers.loginCustomerReq:
|
||||||
properties:
|
properties:
|
||||||
email:
|
|
||||||
example: john.doe@example.com
|
|
||||||
type: string
|
|
||||||
password:
|
password:
|
||||||
example: password123
|
example: password123
|
||||||
type: string
|
type: string
|
||||||
phone_number:
|
user_name:
|
||||||
example: "1234567890"
|
example: johndoe
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- password
|
- password
|
||||||
|
- user_name
|
||||||
type: object
|
type: object
|
||||||
handlers.loginCustomerRes:
|
handlers.loginCustomerRes:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -386,21 +458,6 @@ definitions:
|
||||||
example: false
|
example: false
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
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:
|
response.APIResponse:
|
||||||
properties:
|
properties:
|
||||||
data: {}
|
data: {}
|
||||||
|
|
@ -505,6 +562,39 @@ paths:
|
||||||
summary: Login customer
|
summary: Login customer
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- 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:
|
/api/v1/{tenant_slug}/user/admin-profile:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -596,7 +686,7 @@ paths:
|
||||||
name: registerUser
|
name: registerUser
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.RegisterUserReq'
|
$ref: '#/definitions/domain.RegisterUserReq'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
@ -645,36 +735,6 @@ paths:
|
||||||
summary: Reset tenant password
|
summary: Reset tenant password
|
||||||
tags:
|
tags:
|
||||||
- user
|
- 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:
|
/api/v1/{tenant_slug}/user/sendRegisterCode:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -735,6 +795,36 @@ paths:
|
||||||
summary: Send reset code
|
summary: Send reset code
|
||||||
tags:
|
tags:
|
||||||
- user
|
- 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:
|
/api/v1/admin:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -980,40 +1070,6 @@ paths:
|
||||||
summary: Retrieve application logs with filtering and pagination
|
summary: Retrieve application logs with filtering and pagination
|
||||||
tags:
|
tags:
|
||||||
- Logs
|
- 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:
|
/api/v1/super-login:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1144,6 +1200,35 @@ paths:
|
||||||
summary: Check if phone number or email exist
|
summary: Check if phone number or email exist
|
||||||
tags:
|
tags:
|
||||||
- user
|
- 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}:
|
/api/v1/user/delete/{id}:
|
||||||
delete:
|
delete:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1221,7 +1306,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.UserProfileRes'
|
$ref: '#/definitions/domain.UserProfileResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1280,7 +1365,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.UserProfileRes'
|
$ref: '#/definitions/domain.UserProfileResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1296,36 +1381,6 @@ paths:
|
||||||
summary: Get user by id
|
summary: Get user by id
|
||||||
tags:
|
tags:
|
||||||
- user
|
- 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:
|
securityDefinitions:
|
||||||
Bearer:
|
Bearer:
|
||||||
in: header
|
in: header
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ type AssessmentSubmission struct {
|
||||||
|
|
||||||
type Course struct {
|
type Course struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
OrganizationID int64 `json:"organization_id"`
|
|
||||||
InstructorID int64 `json:"instructor_id"`
|
InstructorID int64 `json:"instructor_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
|
|
@ -97,26 +96,9 @@ type Notification struct {
|
||||||
ReadAt pgtype.Timestamptz `json:"read_at"`
|
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 {
|
type Otp struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
UserName string `json:"user_name"`
|
||||||
SentTo string `json:"sent_to"`
|
SentTo string `json:"sent_to"`
|
||||||
Medium string `json:"medium"`
|
Medium string `json:"medium"`
|
||||||
OtpFor string `json:"otp_for"`
|
OtpFor string `json:"otp_for"`
|
||||||
|
|
@ -127,19 +109,6 @@ type Otp struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
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 {
|
type RefreshToken struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
@ -163,31 +132,25 @@ type ReportedIssue struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Password []byte `json:"password"`
|
Password []byte `json:"password"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
Suspended bool `json:"suspended"`
|
Status string `json:"status"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
ProfilePictureUrl pgtype.Text `json:"profile_picture_url"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
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
|
const CreateOtp = `-- name: CreateOtp :exec
|
||||||
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||||
VALUES ($1, $2, $3, $4, FALSE, $5, $6)
|
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7)
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateOtpParams struct {
|
type CreateOtpParams struct {
|
||||||
|
UserName string `json:"user_name"`
|
||||||
SentTo string `json:"sent_to"`
|
SentTo string `json:"sent_to"`
|
||||||
Medium string `json:"medium"`
|
Medium string `json:"medium"`
|
||||||
OtpFor string `json:"otp_for"`
|
OtpFor string `json:"otp_for"`
|
||||||
|
|
@ -27,6 +28,7 @@ type CreateOtpParams struct {
|
||||||
|
|
||||||
func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
||||||
_, err := q.db.Exec(ctx, CreateOtp,
|
_, err := q.db.Exec(ctx, CreateOtp,
|
||||||
|
arg.UserName,
|
||||||
arg.SentTo,
|
arg.SentTo,
|
||||||
arg.Medium,
|
arg.Medium,
|
||||||
arg.OtpFor,
|
arg.OtpFor,
|
||||||
|
|
@ -38,20 +40,15 @@ func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetOtp = `-- name: GetOtp :one
|
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
|
FROM otps
|
||||||
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
WHERE user_name = $1
|
||||||
ORDER BY created_at DESC LIMIT 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 {
|
type GetOtpRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
UserName string `json:"user_name"`
|
||||||
SentTo string `json:"sent_to"`
|
SentTo string `json:"sent_to"`
|
||||||
Medium string `json:"medium"`
|
Medium string `json:"medium"`
|
||||||
OtpFor string `json:"otp_for"`
|
OtpFor string `json:"otp_for"`
|
||||||
|
|
@ -62,11 +59,12 @@ type GetOtpRow struct {
|
||||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetOtp(ctx context.Context, arg GetOtpParams) (GetOtpRow, error) {
|
func (q *Queries) GetOtp(ctx context.Context, userName string) (GetOtpRow, error) {
|
||||||
row := q.db.QueryRow(ctx, GetOtp, arg.SentTo, arg.OtpFor, arg.Medium)
|
row := q.db.QueryRow(ctx, GetOtp, userName)
|
||||||
var i GetOtpRow
|
var i GetOtpRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.UserName,
|
||||||
&i.SentTo,
|
&i.SentTo,
|
||||||
&i.Medium,
|
&i.Medium,
|
||||||
&i.OtpFor,
|
&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)
|
_, err := q.db.Exec(ctx, MarkOtpAsUsed, arg.ID, arg.UsedAt)
|
||||||
return err
|
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,26 +12,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one
|
const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one
|
||||||
SELECT EXISTS (
|
SELECT
|
||||||
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM users
|
FROM users u1
|
||||||
WHERE users.phone_number = $1
|
WHERE u1.phone_number = $1
|
||||||
AND users.phone_number IS NOT NULL
|
|
||||||
AND users.organization_id = $2
|
|
||||||
) AS phone_exists,
|
) AS phone_exists,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM users
|
FROM users u2
|
||||||
WHERE users.email = $3
|
WHERE u2.email = $2
|
||||||
AND users.email IS NOT NULL
|
|
||||||
AND users.organization_id = $2
|
|
||||||
) AS email_exists
|
) AS email_exists
|
||||||
`
|
`
|
||||||
|
|
||||||
type CheckPhoneEmailExistParams struct {
|
type CheckPhoneEmailExistParams struct {
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
Email pgtype.Text `json:"email"`
|
||||||
Email pgtype.Text `json:"email"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckPhoneEmailExistRow struct {
|
type CheckPhoneEmailExistRow struct {
|
||||||
|
|
@ -40,7 +36,7 @@ type CheckPhoneEmailExistRow struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailExistParams) (CheckPhoneEmailExistRow, error) {
|
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
|
var i CheckPhoneEmailExistRow
|
||||||
err := row.Scan(&i.PhoneExists, &i.EmailExists)
|
err := row.Scan(&i.PhoneExists, &i.EmailExists)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -48,49 +44,48 @@ func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailE
|
||||||
|
|
||||||
const CreateUser = `-- name: CreateUser :one
|
const CreateUser = `-- name: CreateUser :one
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
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
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
$1,
|
|
||||||
$2,
|
|
||||||
$3,
|
|
||||||
$4,
|
|
||||||
$5,
|
|
||||||
$6,
|
|
||||||
$7,
|
|
||||||
$8,
|
|
||||||
$9,
|
|
||||||
$10,
|
|
||||||
$11,
|
|
||||||
$12,
|
|
||||||
$13,
|
|
||||||
$14,
|
|
||||||
$15,
|
|
||||||
$16,
|
|
||||||
$17,
|
|
||||||
$18
|
|
||||||
)
|
|
||||||
RETURNING id,
|
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
|
email,
|
||||||
|
phone_number,
|
||||||
|
role,
|
||||||
|
password,
|
||||||
|
age,
|
||||||
|
education_level,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
email_verified,
|
||||||
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$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,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -100,60 +95,58 @@ RETURNING id,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateUserParams struct {
|
type CreateUserParams struct {
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Password []byte `json:"password"`
|
Password []byte `json:"password"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
Suspended bool `json:"suspended"`
|
Status string `json:"status"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserRow struct {
|
type CreateUserRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
Status string `json:"status"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
Suspended bool `json:"suspended"`
|
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
|
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
|
||||||
row := q.db.QueryRow(ctx, CreateUser,
|
row := q.db.QueryRow(ctx, CreateUser,
|
||||||
arg.FirstName,
|
arg.FirstName,
|
||||||
arg.LastName,
|
arg.LastName,
|
||||||
arg.NickName,
|
arg.UserName,
|
||||||
arg.Email,
|
arg.Email,
|
||||||
arg.PhoneNumber,
|
arg.PhoneNumber,
|
||||||
arg.Role,
|
arg.Role,
|
||||||
|
|
@ -164,18 +157,16 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
|
||||||
arg.Region,
|
arg.Region,
|
||||||
arg.EmailVerified,
|
arg.EmailVerified,
|
||||||
arg.PhoneVerified,
|
arg.PhoneVerified,
|
||||||
arg.Suspended,
|
arg.Status,
|
||||||
arg.SuspendedAt,
|
arg.ProfileCompleted,
|
||||||
arg.OrganizationID,
|
arg.PreferredLanguage,
|
||||||
arg.CreatedAt,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
)
|
)
|
||||||
var i CreateUserRow
|
var i CreateUserRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.NickName,
|
&i.UserName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
|
@ -185,11 +176,11 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU
|
||||||
&i.Region,
|
&i.Region,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.PhoneVerified,
|
&i.PhoneVerified,
|
||||||
|
&i.Status,
|
||||||
|
&i.ProfileCompleted,
|
||||||
|
&i.PreferredLanguage,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Suspended,
|
|
||||||
&i.SuspendedAt,
|
|
||||||
&i.OrganizationID,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +201,7 @@ SELECT
|
||||||
id,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -220,77 +211,71 @@ SELECT
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (
|
WHERE (
|
||||||
role = $1
|
role = $1 OR $1 IS NULL
|
||||||
OR $1 IS NULL
|
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
organization_id = $2
|
first_name ILIKE '%' || $2 || '%'
|
||||||
OR $2 IS NULL
|
OR last_name ILIKE '%' || $2 || '%'
|
||||||
|
OR phone_number ILIKE '%' || $2 || '%'
|
||||||
|
OR email ILIKE '%' || $2 || '%'
|
||||||
|
OR $2 IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
first_name ILIKE '%' || $3 || '%'
|
created_at >= $3
|
||||||
OR last_name ILIKE '%' || $3 || '%'
|
|
||||||
OR phone_number ILIKE '%' || $3 || '%'
|
|
||||||
OR $3 IS NULL
|
OR $3 IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
created_at > $4
|
created_at <= $4
|
||||||
OR $4 IS NULL
|
OR $4 IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
LIMIT $6
|
||||||
created_at < $5
|
OFFSET $5
|
||||||
OR $5 IS NULL
|
|
||||||
)
|
|
||||||
LIMIT $7
|
|
||||||
OFFSET $6
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetAllUsersParams struct {
|
type GetAllUsersParams struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
Query pgtype.Text `json:"query"`
|
||||||
Query pgtype.Text `json:"query"`
|
CreatedAfter pgtype.Timestamptz `json:"created_after"`
|
||||||
CreatedBefore pgtype.Timestamptz `json:"created_before"`
|
CreatedBefore pgtype.Timestamptz `json:"created_before"`
|
||||||
CreatedAfter pgtype.Timestamptz `json:"created_after"`
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
Offset pgtype.Int4 `json:"offset"`
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
Limit pgtype.Int4 `json:"limit"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAllUsersRow struct {
|
type GetAllUsersRow struct {
|
||||||
TotalCount int64 `json:"total_count"`
|
TotalCount int64 `json:"total_count"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
Status string `json:"status"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
Suspended bool `json:"suspended"`
|
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]GetAllUsersRow, error) {
|
func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]GetAllUsersRow, error) {
|
||||||
rows, err := q.db.Query(ctx, GetAllUsers,
|
rows, err := q.db.Query(ctx, GetAllUsers,
|
||||||
arg.Role,
|
arg.Role,
|
||||||
arg.OrganizationID,
|
|
||||||
arg.Query,
|
arg.Query,
|
||||||
arg.CreatedBefore,
|
|
||||||
arg.CreatedAfter,
|
arg.CreatedAfter,
|
||||||
|
arg.CreatedBefore,
|
||||||
arg.Offset,
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
|
|
@ -306,7 +291,7 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.NickName,
|
&i.UserName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
|
@ -316,11 +301,11 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
||||||
&i.Region,
|
&i.Region,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.PhoneVerified,
|
&i.PhoneVerified,
|
||||||
|
&i.Status,
|
||||||
|
&i.ProfileCompleted,
|
||||||
|
&i.PreferredLanguage,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Suspended,
|
|
||||||
&i.SuspendedAt,
|
|
||||||
&i.OrganizationID,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -332,60 +317,14 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
|
||||||
return items, nil
|
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
|
const GetTotalUsers = `-- name: GetTotalUsers :one
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM users
|
FROM users
|
||||||
wHERE (
|
WHERE (role = $1 OR $1 IS NULL)
|
||||||
role = $1
|
|
||||||
OR $1 IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
organization_id = $2
|
|
||||||
OR $2 IS NULL
|
|
||||||
)
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTotalUsersParams struct {
|
func (q *Queries) GetTotalUsers(ctx context.Context, role string) (int64, error) {
|
||||||
Role string `json:"role"`
|
row := q.db.QueryRow(ctx, GetTotalUsers, 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)
|
|
||||||
var count int64
|
var count int64
|
||||||
err := row.Scan(&count)
|
err := row.Scan(&count)
|
||||||
return count, err
|
return count, err
|
||||||
|
|
@ -396,67 +335,67 @@ SELECT
|
||||||
id,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
nick_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
password, -- added this line
|
password,
|
||||||
age,
|
age,
|
||||||
education_level,
|
education_level,
|
||||||
country,
|
country,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
|
last_login,
|
||||||
|
profile_picture_url,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE organization_id = $3
|
WHERE (email = $1 AND $1 IS NOT NULL)
|
||||||
AND (
|
|
||||||
(email = $1 AND $1 IS NOT NULL)
|
|
||||||
OR (phone_number = $2 AND $2 IS NOT NULL)
|
OR (phone_number = $2 AND $2 IS NOT NULL)
|
||||||
)
|
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUserByEmailPhoneParams struct {
|
type GetUserByEmailPhoneParams struct {
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserByEmailPhoneRow struct {
|
type GetUserByEmailPhoneRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Password []byte `json:"password"`
|
Password []byte `json:"password"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
Status string `json:"status"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
Suspended bool `json:"suspended"`
|
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
ProfilePictureUrl pgtype.Text `json:"profile_picture_url"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
PreferredLanguage pgtype.Text `json:"preferred_language"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (GetUserByEmailPhoneRow, error) {
|
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
|
var i GetUserByEmailPhoneRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.NickName,
|
&i.UserName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
|
@ -467,17 +406,19 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
|
||||||
&i.Region,
|
&i.Region,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.PhoneVerified,
|
&i.PhoneVerified,
|
||||||
|
&i.Status,
|
||||||
|
&i.ProfileCompleted,
|
||||||
|
&i.LastLogin,
|
||||||
|
&i.ProfilePictureUrl,
|
||||||
|
&i.PreferredLanguage,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Suspended,
|
|
||||||
&i.SuspendedAt,
|
|
||||||
&i.OrganizationID,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetUserByID = `-- name: GetUserByID :one
|
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
|
FROM users
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -489,7 +430,7 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.NickName,
|
&i.UserName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
|
@ -500,20 +441,133 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
||||||
&i.Region,
|
&i.Region,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.PhoneVerified,
|
&i.PhoneVerified,
|
||||||
&i.Suspended,
|
&i.Status,
|
||||||
&i.SuspendedAt,
|
&i.LastLogin,
|
||||||
&i.OrganizationID,
|
&i.ProfileCompleted,
|
||||||
|
&i.ProfilePictureUrl,
|
||||||
|
&i.PreferredLanguage,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many
|
const GetUserByUserName = `-- name: GetUserByUserName :one
|
||||||
SELECT id,
|
SELECT
|
||||||
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
last_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,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
role,
|
role,
|
||||||
|
|
@ -523,56 +577,50 @@ SELECT id,
|
||||||
region,
|
region,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
|
status,
|
||||||
|
profile_completed,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
suspended,
|
|
||||||
suspended_at,
|
|
||||||
organization_id
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (
|
WHERE (
|
||||||
organization_id = $2
|
|
||||||
OR $2 IS NULL
|
|
||||||
)
|
|
||||||
AND (
|
|
||||||
first_name ILIKE '%' || $1 || '%'
|
first_name ILIKE '%' || $1 || '%'
|
||||||
OR last_name ILIKE '%' || $1 || '%'
|
OR last_name ILIKE '%' || $1 || '%'
|
||||||
OR phone_number LIKE '%' || $1 || '%'
|
OR phone_number ILIKE '%' || $1 || '%'
|
||||||
|
OR email ILIKE '%' || $1 || '%'
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
role = $3
|
role = $2
|
||||||
OR $3 IS NULL
|
OR $2 IS NULL
|
||||||
)
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
type SearchUserByNameOrPhoneParams struct {
|
type SearchUserByNameOrPhoneParams struct {
|
||||||
Column1 pgtype.Text `json:"column_1"`
|
Column1 pgtype.Text `json:"column_1"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
Role pgtype.Text `json:"role"`
|
||||||
Role pgtype.Text `json:"role"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchUserByNameOrPhoneRow struct {
|
type SearchUserByNameOrPhoneRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
NickName pgtype.Text `json:"nick_name"`
|
UserName string `json:"user_name"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Age pgtype.Int4 `json:"age"`
|
Age pgtype.Int4 `json:"age"`
|
||||||
EducationLevel pgtype.Text `json:"education_level"`
|
EducationLevel pgtype.Text `json:"education_level"`
|
||||||
Country pgtype.Text `json:"country"`
|
Country pgtype.Text `json:"country"`
|
||||||
Region pgtype.Text `json:"region"`
|
Region pgtype.Text `json:"region"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
PhoneVerified bool `json:"phone_verified"`
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
Status string `json:"status"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
ProfileCompleted bool `json:"profile_completed"`
|
||||||
Suspended bool `json:"suspended"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByNameOrPhoneParams) ([]SearchUserByNameOrPhoneRow, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -584,7 +632,7 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.NickName,
|
&i.UserName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
|
@ -594,11 +642,10 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
||||||
&i.Region,
|
&i.Region,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.PhoneVerified,
|
&i.PhoneVerified,
|
||||||
|
&i.Status,
|
||||||
|
&i.ProfileCompleted,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Suspended,
|
|
||||||
&i.SuspendedAt,
|
|
||||||
&i.OrganizationID,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -610,59 +657,31 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const SuspendUser = `-- name: SuspendUser :exec
|
|
||||||
UPDATE users
|
|
||||||
SET suspended = $1,
|
|
||||||
suspended_at = $2,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE id = $3
|
|
||||||
`
|
|
||||||
|
|
||||||
type SuspendUserParams struct {
|
|
||||||
Suspended bool `json:"suspended"`
|
|
||||||
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) SuspendUser(ctx context.Context, arg SuspendUserParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, SuspendUser, arg.Suspended, arg.SuspendedAt, arg.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const UpdatePassword = `-- name: UpdatePassword :exec
|
const UpdatePassword = `-- name: UpdatePassword :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET password = $1,
|
SET
|
||||||
updated_at = $4
|
password = $1,
|
||||||
WHERE (
|
updated_at = CURRENT_TIMESTAMP
|
||||||
(email = $2 OR phone_number = $3)
|
WHERE email = $2 OR phone_number = $3
|
||||||
AND organization_id = $5
|
|
||||||
)
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdatePasswordParams struct {
|
type UpdatePasswordParams struct {
|
||||||
Password []byte `json:"password"`
|
Password []byte `json:"password"`
|
||||||
Email pgtype.Text `json:"email"`
|
Email pgtype.Text `json:"email"`
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
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 {
|
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdatePassword,
|
_, err := q.db.Exec(ctx, UpdatePassword, arg.Password, arg.Email, arg.PhoneNumber)
|
||||||
arg.Password,
|
|
||||||
arg.Email,
|
|
||||||
arg.PhoneNumber,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.OrganizationID,
|
|
||||||
)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateUser = `-- name: UpdateUser :exec
|
const UpdateUser = `-- name: UpdateUser :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET first_name = $1,
|
SET
|
||||||
last_name = $2,
|
first_name = $1,
|
||||||
suspended = $3,
|
last_name = $2,
|
||||||
|
status = $3,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $4
|
WHERE id = $4
|
||||||
`
|
`
|
||||||
|
|
@ -670,7 +689,7 @@ WHERE id = $4
|
||||||
type UpdateUserParams struct {
|
type UpdateUserParams struct {
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
Suspended bool `json:"suspended"`
|
Status string `json:"status"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -678,24 +697,26 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateUser,
|
_, err := q.db.Exec(ctx, UpdateUser,
|
||||||
arg.FirstName,
|
arg.FirstName,
|
||||||
arg.LastName,
|
arg.LastName,
|
||||||
arg.Suspended,
|
arg.Status,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateUserOrganization = `-- name: UpdateUserOrganization :exec
|
const UpdateUserStatus = `-- name: UpdateUserStatus :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET organization_id = $1
|
SET
|
||||||
|
status = $1,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUserOrganizationParams struct {
|
type UpdateUserStatusParams struct {
|
||||||
OrganizationID pgtype.Int8 `json:"organization_id"`
|
Status string `json:"status"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUserOrganization(ctx context.Context, arg UpdateUserOrganizationParams) error {
|
func (q *Queries) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateUserOrganization, arg.OrganizationID, arg.ID)
|
_, err := q.db.Exec(ctx, UpdateUserStatus, arg.Status, arg.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ var (
|
||||||
// Enabled bool `mapstructure:"Enabled"`
|
// 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 {
|
// type AtlasConfig struct {
|
||||||
// BaseURL string `mapstructure:"ATLAS_BASE_URL"`
|
// BaseURL string `mapstructure:"ATLAS_BASE_URL"`
|
||||||
// SecretKey string `mapstructure:"ATLAS_SECRET_KEY"`
|
// SecretKey string `mapstructure:"ATLAS_SECRET_KEY"`
|
||||||
|
|
@ -120,6 +126,7 @@ type TELEBIRRConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
AFROSMSConfig AFROSMSConfig `mapstructure:"afro_sms_config"`
|
||||||
APP_VERSION string
|
APP_VERSION string
|
||||||
FIXER_API_KEY string
|
FIXER_API_KEY string
|
||||||
FIXER_BASE_URL string
|
FIXER_BASE_URL string
|
||||||
|
|
@ -248,6 +255,11 @@ func (c *Config) loadEnv() error {
|
||||||
return ErrInvalidLevel
|
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
|
//Telebirr
|
||||||
c.TELEBIRR.TelebirrBaseURL = os.Getenv("TELEBIRR_BASE_URL")
|
c.TELEBIRR.TelebirrBaseURL = os.Getenv("TELEBIRR_BASE_URL")
|
||||||
c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_APP_SECRET")
|
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")
|
c.ADRO_SMS_HOST_URL = os.Getenv("ADRO_SMS_HOST_URL")
|
||||||
if c.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
|
//Atlas
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,9 @@ const (
|
||||||
OtpMediumSms OtpMedium = "sms"
|
OtpMediumSms OtpMedium = "sms"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type Otp struct {
|
type Otp struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
UserName string
|
||||||
SentTo string
|
SentTo string
|
||||||
Medium OtpMedium
|
Medium OtpMedium
|
||||||
For OtpFor
|
For OtpFor
|
||||||
|
|
@ -39,3 +38,12 @@ type Otp struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
ExpiresAt 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 (
|
var (
|
||||||
ErrUserNotFound = errors.New("user not found")
|
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 {
|
type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
NickName string
|
UserName string
|
||||||
Email string `json:"email"`
|
Email string
|
||||||
PhoneNumber string `json:"phone_number"`
|
PhoneNumber string
|
||||||
Password []byte
|
Password []byte
|
||||||
Role Role
|
Role Role
|
||||||
|
|
||||||
Age int
|
Age int
|
||||||
EducationLevel string
|
EducationLevel string
|
||||||
Country string
|
Country string
|
||||||
Region string
|
Region string
|
||||||
EmailVerified bool
|
|
||||||
PhoneVerified bool
|
EmailVerified bool
|
||||||
Suspended bool
|
PhoneVerified bool
|
||||||
SuspendedAt time.Time
|
Status UserStatus
|
||||||
OrganizationID ValidInt64
|
|
||||||
CreatedAt time.Time
|
LastLogin *time.Time
|
||||||
UpdatedAt time.Time
|
ProfileCompleted bool
|
||||||
|
ProfilePictureURL string
|
||||||
|
PreferredLanguage string
|
||||||
|
|
||||||
|
CreatedAt 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 {
|
type UserFilter struct {
|
||||||
Role string
|
Role string
|
||||||
OrganizationID ValidInt64
|
|
||||||
Page ValidInt
|
Page ValidInt
|
||||||
PageSize ValidInt
|
PageSize ValidInt
|
||||||
Query ValidString
|
Query ValidString
|
||||||
|
|
@ -42,92 +87,62 @@ type UserFilter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisterUserReq struct {
|
type RegisterUserReq struct {
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
NickName string
|
UserName string
|
||||||
Email string
|
Email string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
Password string
|
Password string
|
||||||
Role string
|
Role string
|
||||||
Otp string
|
|
||||||
ReferralCode string `json:"referral_code"`
|
OtpMedium OtpMedium
|
||||||
OtpMedium OtpMedium
|
|
||||||
OrganizationID ValidInt64
|
Age int
|
||||||
Age int
|
EducationLevel string
|
||||||
EducationLevel string
|
Country string
|
||||||
Country string
|
Region string
|
||||||
Region string
|
PreferredLanguage string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserReq struct {
|
type CreateUserReq struct {
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
NickName string
|
UserName string
|
||||||
Email string
|
Email string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
Password string
|
Password string
|
||||||
Role string
|
Role string
|
||||||
Suspended bool
|
|
||||||
OrganizationID ValidInt64
|
Status UserStatus
|
||||||
Age int
|
|
||||||
EducationLevel string
|
Age int
|
||||||
Country string
|
EducationLevel string
|
||||||
Region string
|
Country string
|
||||||
|
Region string
|
||||||
|
PreferredLanguage string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResetPasswordReq struct {
|
type ResetPasswordReq struct {
|
||||||
Email string
|
UserName string
|
||||||
PhoneNumber string
|
Password string
|
||||||
Password string
|
OtpCode string
|
||||||
Otp string
|
|
||||||
OtpMedium OtpMedium
|
|
||||||
OrganizationID int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserReq struct {
|
type UpdateUserReq struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
FirstName ValidString
|
|
||||||
LastName ValidString
|
FirstName ValidString
|
||||||
NickName ValidString
|
LastName ValidString
|
||||||
Suspended ValidBool
|
UserName ValidString
|
||||||
OrganizationID ValidInt64
|
|
||||||
|
Status ValidString
|
||||||
|
|
||||||
Age ValidInt
|
Age ValidInt
|
||||||
EducationLevel ValidString
|
EducationLevel ValidString
|
||||||
Country ValidString
|
Country ValidString
|
||||||
Region 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 {
|
type UserStore interface {
|
||||||
CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error)
|
IsUserNameUnique(ctx context.Context, userName string) (bool, error)
|
||||||
CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error)
|
IsUserPending(ctx context.Context, UserName string) (bool, error)
|
||||||
GetUserByID(ctx context.Context, id int64) (domain.User, 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(
|
GetAllUsers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
role *string,
|
role *string,
|
||||||
organizationID *int64,
|
|
||||||
query *string,
|
query *string,
|
||||||
createdBefore, createdAfter *time.Time,
|
createdBefore, createdAfter *time.Time,
|
||||||
limit, offset int32,
|
limit, offset int32,
|
||||||
) ([]domain.User, int64, error)
|
) ([]domain.User, int64, error)
|
||||||
GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error)
|
GetTotalUsers(ctx context.Context, role *string) (int64, error)
|
||||||
SearchUserByNameOrPhone(ctx context.Context, search string, organizationID *int64, role *string) ([]domain.User, error)
|
SearchUserByNameOrPhone(
|
||||||
|
ctx context.Context,
|
||||||
|
search string,
|
||||||
|
role *string,
|
||||||
|
) ([]domain.User, error)
|
||||||
UpdateUser(ctx context.Context, user 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
|
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(
|
GetUserByEmailPhone(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
email string,
|
email string,
|
||||||
phone string,
|
phone string,
|
||||||
organizationID domain.ValidInt64,
|
|
||||||
) (domain.User, error)
|
) (domain.User, error)
|
||||||
UpdatePassword(ctx context.Context, password, email, phone string, organizationID int64, updatedAt time.Time) 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
|
// 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
|
// UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||||
// UpdateUserSuspend(ctx context.Context, id int64, status bool) 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
|
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||||
}
|
}
|
||||||
type OtpStore interface {
|
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
|
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"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
dbgen "Yimaru-Backend/gen/db"
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
"Yimaru-Backend/internal/domain"
|
"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
|
// 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) {
|
func (s *Store) GetUserByEmailOrPhone(
|
||||||
// prepare organizationID param for the query
|
ctx context.Context,
|
||||||
// var orgParam pgtype.Int8
|
email string,
|
||||||
// if organizationID != nil {
|
phone string,
|
||||||
// orgParam = pgtype.Int8{Int64: *organizationID}
|
) (domain.User, error) {
|
||||||
// } else {
|
|
||||||
// orgParam = pgtype.Int8{Status: pgtype.Null}
|
|
||||||
// }
|
|
||||||
|
|
||||||
u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||||
Email: pgtype.Text{String: email, Valid: email != ""},
|
Email: pgtype.Text{
|
||||||
PhoneNumber: pgtype.Text{String: phone, Valid: phone != ""},
|
String: email,
|
||||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
Valid: email != "",
|
||||||
|
},
|
||||||
|
PhoneNumber: pgtype.Text{
|
||||||
|
String: phone,
|
||||||
|
Valid: phone != "",
|
||||||
|
},
|
||||||
|
// OrganizationID: pgtype.Int8{Int64: organizationID},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
|
@ -94,20 +98,45 @@ func (s *Store) GetUserByEmailOrPhone(ctx context.Context, email, phone string,
|
||||||
return domain.User{}, err
|
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{
|
return domain.User{
|
||||||
ID: u.ID,
|
ID: u.ID,
|
||||||
FirstName: u.FirstName,
|
FirstName: u.FirstName,
|
||||||
LastName: u.LastName,
|
LastName: u.LastName,
|
||||||
Email: u.Email.String,
|
UserName: u.UserName,
|
||||||
PhoneNumber: u.PhoneNumber.String,
|
Email: u.Email.String,
|
||||||
Role: domain.Role(u.Role),
|
PhoneNumber: u.PhoneNumber.String,
|
||||||
Password: u.Password,
|
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,
|
EmailVerified: u.EmailVerified,
|
||||||
PhoneVerified: u.PhoneVerified,
|
PhoneVerified: u.PhoneVerified,
|
||||||
Suspended: u.Suspended,
|
Status: domain.UserStatus(u.Status),
|
||||||
SuspendedAt: u.SuspendedAt.Time,
|
|
||||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
LastLogin: lastLogin,
|
||||||
CreatedAt: u.CreatedAt.Time,
|
ProfileCompleted: u.ProfileCompleted,
|
||||||
UpdatedAt: u.UpdatedAt.Time,
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
dbgen "Yimaru-Backend/gen/db"
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
|
|
@ -155,7 +156,7 @@ func mapDBToDomain(db *dbgen.Notification) *domain.Notification {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.Notification{
|
return &domain.Notification{
|
||||||
ID: string(db.ID),
|
ID: strconv.FormatInt(db.ID, 10),
|
||||||
RecipientID: db.UserID,
|
RecipientID: db.UserID,
|
||||||
Type: domain.NotificationType(db.Type),
|
Type: domain.NotificationType(db.Type),
|
||||||
Level: domain.NotificationLevel(db.Level),
|
Level: domain.NotificationLevel(db.Level),
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,29 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
dbgen "Yimaru-Backend/gen/db"
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"Yimaru-Backend/internal/ports"
|
"Yimaru-Backend/internal/ports"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface for creating new otp store
|
// Interface for creating new otp store
|
||||||
func NewOTPStore(s *Store) ports.OtpStore { return s }
|
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 {
|
func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error {
|
||||||
return s.queries.CreateOtp(ctx, dbgen.CreateOtpParams{
|
return s.queries.CreateOtp(ctx, dbgen.CreateOtpParams{
|
||||||
SentTo: otp.SentTo,
|
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) {
|
func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error) {
|
||||||
row, err := s.queries.GetOtp(ctx, dbgen.GetOtpParams{
|
row, err := s.queries.GetOtp(ctx, userName)
|
||||||
SentTo: sentTo,
|
|
||||||
Medium: string(medium),
|
|
||||||
OtpFor: string(sentfor),
|
|
||||||
})
|
|
||||||
if err != nil {
|
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 {
|
if err == sql.ErrNoRows {
|
||||||
return domain.Otp{}, domain.ErrOtpNotFound
|
return domain.Otp{}, domain.ErrOtpNotFound
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,129 +14,184 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"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 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{
|
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
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 != ""},
|
Email: pgtype.Text{String: user.Email, Valid: user.Email != ""},
|
||||||
Role: string(user.Role),
|
PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""},
|
||||||
Password: user.Password,
|
|
||||||
|
Role: string(user.Role),
|
||||||
|
Password: user.Password,
|
||||||
|
|
||||||
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
||||||
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
||||||
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
||||||
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
||||||
EmailVerified: user.EmailVerified,
|
|
||||||
PhoneVerified: user.PhoneVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
Suspended: user.Suspended,
|
PhoneVerified: user.PhoneVerified,
|
||||||
SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()},
|
|
||||||
OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid},
|
Status: string(user.Status),
|
||||||
CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
ProfileCompleted: user.ProfileCompleted,
|
||||||
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
PreferredLanguage: pgtype.Text{
|
||||||
|
String: user.PreferredLanguage,
|
||||||
|
Valid: user.PreferredLanguage != "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// OrganizationID: user.OrganizationID.ToPG(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updatedAt *time.Time
|
||||||
|
if userRes.UpdatedAt.Valid {
|
||||||
|
updatedAt = &userRes.UpdatedAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
return domain.User{
|
return domain.User{
|
||||||
ID: userRes.ID,
|
ID: userRes.ID,
|
||||||
FirstName: userRes.FirstName,
|
FirstName: userRes.FirstName,
|
||||||
LastName: userRes.LastName,
|
LastName: userRes.LastName,
|
||||||
NickName: userRes.NickName.String,
|
UserName: userRes.UserName,
|
||||||
Email: userRes.Email.String,
|
Email: userRes.Email.String,
|
||||||
PhoneNumber: userRes.PhoneNumber.String,
|
PhoneNumber: userRes.PhoneNumber.String,
|
||||||
Role: domain.Role(userRes.Role),
|
Role: domain.Role(userRes.Role),
|
||||||
|
Password: user.Password,
|
||||||
|
|
||||||
Age: int(userRes.Age.Int32),
|
Age: int(userRes.Age.Int32),
|
||||||
EducationLevel: userRes.EducationLevel.String,
|
EducationLevel: userRes.EducationLevel.String,
|
||||||
Country: userRes.Country.String,
|
Country: userRes.Country.String,
|
||||||
Region: userRes.Region.String,
|
Region: userRes.Region.String,
|
||||||
EmailVerified: userRes.EmailVerified,
|
|
||||||
PhoneVerified: userRes.PhoneVerified,
|
EmailVerified: userRes.EmailVerified,
|
||||||
Suspended: userRes.Suspended,
|
PhoneVerified: userRes.PhoneVerified,
|
||||||
SuspendedAt: userRes.SuspendedAt.Time,
|
Status: domain.UserStatus(userRes.Status),
|
||||||
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
|
|
||||||
CreatedAt: userRes.CreatedAt.Time,
|
ProfileCompleted: userRes.ProfileCompleted,
|
||||||
UpdatedAt: userRes.UpdatedAt.Time,
|
PreferredLanguage: userRes.PreferredLanguage.String,
|
||||||
|
|
||||||
|
CreatedAt: userRes.CreatedAt.Time,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser inserts a new user into the database
|
// 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
|
// Optional: mark OTP as used
|
||||||
if usedOtpId > 0 {
|
if usedOtpId > 0 {
|
||||||
err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
|
if err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{
|
||||||
ID: usedOtpId,
|
ID: usedOtpId,
|
||||||
UsedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
UsedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
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 != ""},
|
Email: pgtype.Text{String: user.Email, Valid: user.Email != ""},
|
||||||
Role: string(user.Role),
|
PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""},
|
||||||
Password: user.Password,
|
|
||||||
|
Role: string(user.Role),
|
||||||
|
Password: user.Password,
|
||||||
|
|
||||||
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
|
||||||
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
|
||||||
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
|
||||||
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
|
||||||
EmailVerified: user.EmailVerified,
|
|
||||||
PhoneVerified: user.PhoneVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
Suspended: user.Suspended,
|
PhoneVerified: user.PhoneVerified,
|
||||||
SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()},
|
|
||||||
OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid},
|
Status: string(user.Status),
|
||||||
CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
ProfileCompleted: user.ProfileCompleted,
|
||||||
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
PreferredLanguage: pgtype.Text{
|
||||||
|
String: user.PreferredLanguage,
|
||||||
|
Valid: user.PreferredLanguage != "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// OrganizationID: user.OrganizationID.ToPG(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updatedAt *time.Time
|
||||||
|
if userRes.UpdatedAt.Valid {
|
||||||
|
updatedAt = &userRes.UpdatedAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
return domain.User{
|
return domain.User{
|
||||||
ID: userRes.ID,
|
ID: userRes.ID,
|
||||||
FirstName: userRes.FirstName,
|
FirstName: userRes.FirstName,
|
||||||
LastName: userRes.LastName,
|
LastName: userRes.LastName,
|
||||||
NickName: userRes.NickName.String,
|
UserName: userRes.UserName,
|
||||||
Email: userRes.Email.String,
|
Email: userRes.Email.String,
|
||||||
PhoneNumber: userRes.PhoneNumber.String,
|
PhoneNumber: userRes.PhoneNumber.String,
|
||||||
Role: domain.Role(userRes.Role),
|
Role: domain.Role(userRes.Role),
|
||||||
|
Password: user.Password,
|
||||||
|
|
||||||
Age: int(userRes.Age.Int32),
|
Age: int(userRes.Age.Int32),
|
||||||
EducationLevel: userRes.EducationLevel.String,
|
EducationLevel: userRes.EducationLevel.String,
|
||||||
Country: userRes.Country.String,
|
Country: userRes.Country.String,
|
||||||
Region: userRes.Region.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
|
|
||||||
|
|
||||||
|
EmailVerified: userRes.EmailVerified,
|
||||||
|
PhoneVerified: userRes.PhoneVerified,
|
||||||
|
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
|
// GetUserByID retrieves a user by ID
|
||||||
func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
func (s *Store) GetUserByID(
|
||||||
userRes, err := s.queries.GetUserByID(ctx, id)
|
ctx context.Context,
|
||||||
|
id int64,
|
||||||
|
) (domain.User, error) {
|
||||||
|
|
||||||
|
u, err := s.queries.GetUserByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return domain.User{}, domain.ErrUserNotFound
|
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{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.User{
|
var lastLogin *time.Time
|
||||||
ID: userRes.ID,
|
if u.LastLogin.Valid {
|
||||||
FirstName: userRes.FirstName,
|
lastLogin = &u.LastLogin.Time
|
||||||
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 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
|
// GetAllUsers retrieves users with optional filters
|
||||||
func (s *Store) GetAllUsers(
|
func (s *Store) GetAllUsers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
role *string,
|
role *string,
|
||||||
organizationID *int64,
|
|
||||||
query *string,
|
query *string,
|
||||||
createdBefore, createdAfter *time.Time,
|
createdBefore, createdAfter *time.Time,
|
||||||
limit, offset int32,
|
limit, offset int32,
|
||||||
|
|
@ -186,9 +259,9 @@ func (s *Store) GetAllUsers(
|
||||||
params.Role = *role
|
params.Role = *role
|
||||||
}
|
}
|
||||||
|
|
||||||
if organizationID != nil {
|
// if organizationID != nil {
|
||||||
params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true}
|
// params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true}
|
||||||
}
|
// }
|
||||||
|
|
||||||
if query != nil {
|
if query != nil {
|
||||||
params.Query = pgtype.Text{String: *query, Valid: true}
|
params.Query = pgtype.Text{String: *query, Valid: true}
|
||||||
|
|
@ -212,31 +285,42 @@ func (s *Store) GetAllUsers(
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCount := rows[0].TotalCount
|
totalCount := rows[0].TotalCount
|
||||||
|
|
||||||
users := make([]domain.User, 0, len(rows))
|
users := make([]domain.User, 0, len(rows))
|
||||||
|
|
||||||
for _, u := range rows {
|
for _, u := range rows {
|
||||||
|
|
||||||
|
var updatedAt *time.Time
|
||||||
|
if u.UpdatedAt.Valid {
|
||||||
|
updatedAt = &u.UpdatedAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
users = append(users, domain.User{
|
users = append(users, domain.User{
|
||||||
ID: u.ID,
|
ID: u.ID,
|
||||||
FirstName: u.FirstName,
|
FirstName: u.FirstName,
|
||||||
LastName: u.LastName,
|
LastName: u.LastName,
|
||||||
NickName: u.NickName.String,
|
UserName: u.UserName,
|
||||||
Email: u.Email.String,
|
Email: u.Email.String,
|
||||||
PhoneNumber: u.PhoneNumber.String,
|
PhoneNumber: u.PhoneNumber.String,
|
||||||
Role: domain.Role(u.Role),
|
Role: domain.Role(u.Role),
|
||||||
|
|
||||||
Age: int(u.Age.Int32),
|
Age: int(u.Age.Int32),
|
||||||
EducationLevel: u.EducationLevel.String,
|
EducationLevel: u.EducationLevel.String,
|
||||||
Country: u.Country.String,
|
Country: u.Country.String,
|
||||||
Region: u.Region.String,
|
Region: u.Region.String,
|
||||||
EmailVerified: u.EmailVerified,
|
|
||||||
PhoneVerified: u.PhoneVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
Suspended: u.Suspended,
|
PhoneVerified: u.PhoneVerified,
|
||||||
SuspendedAt: u.SuspendedAt.Time,
|
Status: domain.UserStatus(u.Status),
|
||||||
OrganizationID: domain.ValidInt64{
|
|
||||||
Value: u.OrganizationID.Int64,
|
ProfileCompleted: u.ProfileCompleted,
|
||||||
Valid: u.OrganizationID.Valid,
|
PreferredLanguage: u.PreferredLanguage.String,
|
||||||
},
|
|
||||||
|
// OrganizationID: domain.ValidInt64{
|
||||||
|
// Value: u.OrganizationID.Int64,
|
||||||
|
// Valid: u.OrganizationID.Valid,
|
||||||
|
// },
|
||||||
CreatedAt: u.CreatedAt.Time,
|
CreatedAt: u.CreatedAt.Time,
|
||||||
UpdatedAt: u.UpdatedAt.Time,
|
UpdatedAt: updatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,11 +328,8 @@ func (s *Store) GetAllUsers(
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalUsers counts users with optional filters
|
// GetTotalUsers counts users with optional filters
|
||||||
func (s *Store) GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error) {
|
func (s *Store) GetTotalUsers(ctx context.Context, role *string) (int64, error) {
|
||||||
count, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{
|
count, err := s.queries.GetTotalUsers(ctx, *role)
|
||||||
Role: *role,
|
|
||||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -256,38 +337,73 @@ func (s *Store) GetTotalUsers(ctx context.Context, role *string, organizationID
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchUserByNameOrPhone searches users by name or phone
|
// SearchUserByNameOrPhone searches users by name or phone
|
||||||
func (s *Store) SearchUserByNameOrPhone(ctx context.Context, search string, organizationID *int64, role *string) ([]domain.User, error) {
|
func (s *Store) SearchUserByNameOrPhone(
|
||||||
rows, err := s.queries.SearchUserByNameOrPhone(ctx, dbgen.SearchUserByNameOrPhoneParams{
|
ctx context.Context,
|
||||||
Column1: pgtype.Text{String: search},
|
search string,
|
||||||
OrganizationID: pgtype.Int8{Int64: *organizationID},
|
role *string,
|
||||||
Role: pgtype.Text{String: *role},
|
) ([]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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
users := make([]domain.User, len(rows))
|
users := make([]domain.User, 0, len(rows))
|
||||||
for i, u := range rows {
|
for _, u := range rows {
|
||||||
users[i] = domain.User{
|
|
||||||
ID: u.ID,
|
var updatedAt *time.Time
|
||||||
FirstName: u.FirstName,
|
if u.UpdatedAt.Valid {
|
||||||
LastName: u.LastName,
|
updatedAt = &u.UpdatedAt.Time
|
||||||
NickName: u.NickName.String,
|
}
|
||||||
Email: u.Email.String,
|
|
||||||
PhoneNumber: u.PhoneNumber.String,
|
users = append(users, domain.User{
|
||||||
Role: domain.Role(u.Role),
|
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),
|
Age: int(u.Age.Int32),
|
||||||
EducationLevel: u.EducationLevel.String,
|
EducationLevel: u.EducationLevel.String,
|
||||||
Country: u.Country.String,
|
Country: u.Country.String,
|
||||||
Region: u.Region.String,
|
Region: u.Region.String,
|
||||||
EmailVerified: u.EmailVerified,
|
|
||||||
PhoneVerified: u.PhoneVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
Suspended: u.Suspended,
|
PhoneVerified: u.PhoneVerified,
|
||||||
SuspendedAt: u.SuspendedAt.Time,
|
Status: domain.UserStatus(u.Status),
|
||||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
|
||||||
CreatedAt: u.CreatedAt.Time,
|
ProfileCompleted: u.ProfileCompleted,
|
||||||
UpdatedAt: u.UpdatedAt.Time,
|
|
||||||
}
|
// OrganizationID: domain.ValidInt64{
|
||||||
|
// Value: u.OrganizationID.Int64,
|
||||||
|
// Valid: u.OrganizationID.Valid,
|
||||||
|
// },
|
||||||
|
CreatedAt: u.CreatedAt.Time,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
|
|
@ -296,29 +412,29 @@ func (s *Store) SearchUserByNameOrPhone(ctx context.Context, search string, orga
|
||||||
// UpdateUser updates basic user info
|
// UpdateUser updates basic user info
|
||||||
func (s *Store) UpdateUser(ctx context.Context, user domain.User) error {
|
func (s *Store) UpdateUser(ctx context.Context, user domain.User) error {
|
||||||
return s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
return s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
||||||
|
ID: user.ID,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Suspended: user.Suspended,
|
Status: string(user.Status),
|
||||||
ID: user.ID,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserOrganization updates a user's organization
|
// UpdateUserOrganization updates a user's organization
|
||||||
func (s *Store) UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error {
|
// func (s *Store) UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error {
|
||||||
return s.queries.UpdateUserOrganization(ctx, dbgen.UpdateUserOrganizationParams{
|
// return s.queries.UpdateUserOrganization(ctx, dbgen.UpdateUserOrganizationParams{
|
||||||
OrganizationID: pgtype.Int8{Int64: organizationID, Valid: true},
|
// OrganizationID: pgtype.Int8{Int64: organizationID, Valid: true},
|
||||||
ID: userID,
|
// ID: userID,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SuspendUser suspends a user
|
// SuspendUser suspends a user
|
||||||
func (s *Store) SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error {
|
// func (s *Store) SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error {
|
||||||
return s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
// return s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
||||||
Suspended: suspended,
|
// Suspended: suspended,
|
||||||
SuspendedAt: pgtype.Timestamptz{Time: suspendedAt, Valid: true},
|
// SuspendedAt: pgtype.Timestamptz{Time: suspendedAt, Valid: true},
|
||||||
ID: userID,
|
// ID: userID,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DeleteUser removes a user
|
// DeleteUser removes a user
|
||||||
func (s *Store) DeleteUser(ctx context.Context, userID int64) error {
|
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
|
// 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{
|
res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{
|
||||||
PhoneNumber: pgtype.Text{String: phone},
|
PhoneNumber: pgtype.Text{String: phone},
|
||||||
Email: pgtype.Text{String: email},
|
Email: pgtype.Text{String: email},
|
||||||
OrganizationID: pgtype.Int8{Int64: organizationID.Value},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false, err
|
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
|
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
|
// GetUserByEmail retrieves a user by email and organization
|
||||||
func (s *Store) GetUserByEmailPhone(
|
func (s *Store) GetUserByEmailPhone(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
email string,
|
email string,
|
||||||
phone string,
|
phone string,
|
||||||
organizationID domain.ValidInt64,
|
|
||||||
) (domain.User, error) {
|
) (domain.User, error) {
|
||||||
|
|
||||||
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||||
Email: pgtype.Text{
|
Email: pgtype.Text{
|
||||||
String: email,
|
String: email,
|
||||||
Valid: email != "",
|
Valid: email != "",
|
||||||
|
|
@ -356,7 +525,6 @@ func (s *Store) GetUserByEmailPhone(
|
||||||
String: phone,
|
String: phone,
|
||||||
Valid: phone != "",
|
Valid: phone != "",
|
||||||
},
|
},
|
||||||
OrganizationID: organizationID.ToPG(),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
|
@ -365,87 +533,113 @@ func (s *Store) GetUserByEmailPhone(
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.User{
|
var lastLogin *time.Time
|
||||||
ID: user.ID,
|
if u.LastLogin.Valid {
|
||||||
FirstName: user.FirstName,
|
lastLogin = &u.LastLogin.Time
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
var updatedAt *time.Time
|
||||||
err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
|
if u.UpdatedAt.Valid {
|
||||||
ID: id,
|
updatedAt = &u.UpdatedAt.Time
|
||||||
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{
|
return domain.User{
|
||||||
ID: u.ID,
|
ID: u.ID,
|
||||||
FirstName: u.FirstName,
|
FirstName: u.FirstName,
|
||||||
LastName: u.LastName,
|
LastName: u.LastName,
|
||||||
NickName: u.NickName.String,
|
UserName: u.UserName,
|
||||||
Email: u.Email.String,
|
Email: u.Email.String,
|
||||||
PhoneNumber: u.PhoneNumber.String,
|
PhoneNumber: u.PhoneNumber.String,
|
||||||
Role: domain.Role(u.Role),
|
Password: u.Password,
|
||||||
|
Role: domain.Role(u.Role),
|
||||||
|
|
||||||
Age: int(u.Age.Int32),
|
Age: int(u.Age.Int32),
|
||||||
EducationLevel: u.EducationLevel.String,
|
EducationLevel: u.EducationLevel.String,
|
||||||
Country: u.Country.String,
|
Country: u.Country.String,
|
||||||
Region: u.Region.String,
|
Region: u.Region.String,
|
||||||
EmailVerified: u.EmailVerified,
|
|
||||||
PhoneVerified: u.PhoneVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
Suspended: u.Suspended,
|
PhoneVerified: u.PhoneVerified,
|
||||||
SuspendedAt: u.SuspendedAt.Time,
|
Status: domain.UserStatus(u.Status),
|
||||||
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid},
|
|
||||||
CreatedAt: u.CreatedAt.Time,
|
LastLogin: lastLogin,
|
||||||
UpdatedAt: u.UpdatedAt.Time,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -23,55 +24,65 @@ type LoginSuccess struct {
|
||||||
UserId int64
|
UserId int64
|
||||||
Role domain.Role
|
Role domain.Role
|
||||||
RfToken string
|
RfToken string
|
||||||
CompanyID domain.ValidInt64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Login(ctx context.Context, email, phone string, password string, companyID domain.ValidInt64) (LoginSuccess, error) {
|
func (s *Service) Login(
|
||||||
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone, companyID)
|
ctx context.Context,
|
||||||
|
userName, password string,
|
||||||
|
) (LoginSuccess, error) {
|
||||||
|
|
||||||
|
user, err := s.userStore.GetUserByUserName(ctx, userName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginSuccess{}, err
|
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
|
return LoginSuccess{}, err
|
||||||
}
|
}
|
||||||
if user.Suspended {
|
|
||||||
|
// Status check instead of Suspended
|
||||||
|
if user.Status == domain.UserStatusSuspended {
|
||||||
return LoginSuccess{}, ErrUserSuspended
|
return LoginSuccess{}, ErrUserSuspended
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle existing refresh token
|
||||||
oldRefreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
|
oldRefreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
|
||||||
|
if err != nil && !errors.Is(err, ErrRefreshTokenNotFound) {
|
||||||
if err != nil && err != ErrRefreshTokenNotFound {
|
|
||||||
return LoginSuccess{}, err
|
return LoginSuccess{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If old refresh token is not revoked, revoke it
|
// Revoke if exists and not revoked
|
||||||
if err == nil && !oldRefreshToken.Revoked {
|
if err == nil && !oldRefreshToken.Revoked {
|
||||||
err = s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token)
|
if err := s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token); err != nil {
|
||||||
if err != nil {
|
|
||||||
return LoginSuccess{}, err
|
return LoginSuccess{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate new refresh token
|
||||||
refreshToken, err := generateRefreshToken()
|
refreshToken, err := generateRefreshToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginSuccess{}, err
|
return LoginSuccess{}, err
|
||||||
}
|
}
|
||||||
err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
|
||||||
|
if err := s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
||||||
Token: refreshToken,
|
Token: refreshToken,
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
|
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
|
||||||
})
|
}); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return LoginSuccess{}, err
|
return LoginSuccess{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return login success payload
|
||||||
return LoginSuccess{
|
return LoginSuccess{
|
||||||
UserId: user.ID,
|
UserId: user.ID,
|
||||||
Role: user.Role,
|
Role: user.Role,
|
||||||
RfToken: refreshToken,
|
RfToken: refreshToken,
|
||||||
CompanyID: user.OrganizationID,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ package messenger
|
||||||
import (
|
import (
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
afro "github.com/amanuelabay/afrosms-go"
|
afro "github.com/amanuelabay/afrosms-go"
|
||||||
"github.com/twilio/twilio-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)
|
// 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 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 {
|
switch settingsList.SMSProvider {
|
||||||
case domain.AfroMessage:
|
case domain.AfroMessage:
|
||||||
return s.SendAfroMessageSMS(ctx, receiverPhone, message)
|
return s.SendAfroMessageSMSLatest(ctx, receiverPhone, message, nil)
|
||||||
case domain.TwilioSms:
|
case domain.TwilioSms:
|
||||||
return s.SendTwilioSMS(ctx, receiverPhone, message)
|
return s.SendTwilioSMS(ctx, receiverPhone, message)
|
||||||
default:
|
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 {
|
func (s *Service) SendTwilioSMS(ctx context.Context, receiverPhone, message string) error {
|
||||||
accountSid := s.config.TwilioAccountSid
|
accountSid := s.config.TwilioAccountSid
|
||||||
authToken := s.config.TwilioAuthToken
|
authToken := s.config.TwilioAuthToken
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,17 @@ import (
|
||||||
"Yimaru-Backend/internal/services/user"
|
"Yimaru-Backend/internal/services/user"
|
||||||
"Yimaru-Backend/internal/web_server/ws"
|
"Yimaru-Backend/internal/web_server/ws"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
// "errors"
|
// "errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// "github.com/segmentio/kafka-go"
|
// "github.com/segmentio/kafka-go"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
// afro "github.com/amanuelabay/afrosms-go"
|
// afro "github.com/amanuelabay/afrosms-go"
|
||||||
|
|
@ -67,6 +72,79 @@ func New(
|
||||||
return svc
|
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 {
|
func (s *Service) addConnection(recipientID int64, c *websocket.Conn) error {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
s.mongoLogger.Warn("[NotificationSvc.AddConnection] Attempted to add nil WebSocket connection",
|
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 == "" {
|
if user.PhoneNumber == "" {
|
||||||
return fmt.Errorf("phone Number is invalid")
|
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 {
|
if err != nil {
|
||||||
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS",
|
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS",
|
||||||
zap.Int64("recipient_id", recipientID),
|
zap.Int64("recipient_id", recipientID),
|
||||||
zap.String("user_phone_number", user.PhoneNumber),
|
zap.String("user_phone_number", user.PhoneNumber),
|
||||||
zap.String("message", message),
|
zap.String("message", message),
|
||||||
zap.Int64("company_id", user.OrganizationID.Value),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
@ -371,7 +448,6 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64,
|
||||||
zap.Int64("recipient_id", recipientID),
|
zap.Int64("recipient_id", recipientID),
|
||||||
zap.String("user_email", user.Email),
|
zap.String("user_email", user.Email),
|
||||||
zap.String("message", message),
|
zap.String("message", message),
|
||||||
zap.Int64("company_id", user.OrganizationID.Value),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,51 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"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 {
|
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
|
||||||
otpCode := helpers.GenerateOTP()
|
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)
|
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) {
|
func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,35 @@ import (
|
||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq, is_company bool) (domain.User, error) {
|
func (s *Service) CreateUser(
|
||||||
// Create User
|
ctx context.Context,
|
||||||
// creator, err := s.userStore.GetUserByID(ctx, createrUserId)
|
req domain.CreateUserReq,
|
||||||
// if err != nil {
|
isCompany bool,
|
||||||
// return domain.User{}, err
|
) (domain.User, error) {
|
||||||
// }
|
|
||||||
// if creator.Role != domain.RoleAdmin {
|
|
||||||
// User.BranchID = creator.BranchID
|
|
||||||
// User.Role = string(domain.RoleCashier)
|
|
||||||
// } else {
|
|
||||||
// User.BranchID = branchId
|
|
||||||
// User.Role = string(domain.RoleBranchManager)
|
|
||||||
// }
|
|
||||||
|
|
||||||
hashedPassword, err := hashPassword(User.Password)
|
// Hash the password
|
||||||
|
hashedPassword, err := hashPassword(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the user
|
||||||
return s.userStore.CreateUserWithoutOtp(ctx, domain.User{
|
return s.userStore.CreateUserWithoutOtp(ctx, domain.User{
|
||||||
FirstName: User.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: User.LastName,
|
LastName: req.LastName,
|
||||||
Email: User.Email,
|
UserName: req.UserName,
|
||||||
PhoneNumber: User.PhoneNumber,
|
Email: req.Email,
|
||||||
Password: hashedPassword,
|
PhoneNumber: req.PhoneNumber,
|
||||||
Role: domain.Role(User.Role),
|
Password: hashedPassword,
|
||||||
EmailVerified: true,
|
Role: domain.Role(req.Role),
|
||||||
PhoneVerified: true,
|
EmailVerified: true, // assuming auto-verified on creation
|
||||||
Suspended: User.Suspended,
|
PhoneVerified: true,
|
||||||
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) {
|
func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
|
||||||
// Get all Users
|
// 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) {
|
func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,48 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
// import (
|
import (
|
||||||
// "context"
|
"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 {
|
GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
// CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error)
|
GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error)
|
||||||
// CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
|
GetBranchCustomerCounts(ctx context.Context, filter domain.ReportFilter) (map[int64]int64, error)
|
||||||
// GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
GetRoleCounts(ctx context.Context, role string, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
// GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error)
|
}
|
||||||
// GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error)
|
type SmsGateway interface {
|
||||||
// GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error)
|
SendSMSOTP(ctx context.Context, phoneNumber, otp string) error
|
||||||
// GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
|
}
|
||||||
// GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error)
|
type EmailGateway interface {
|
||||||
// UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||||
// UpdateUserCompany(ctx context.Context, id int64, companyID int64) error
|
}
|
||||||
// UpdateUserSuspend(ctx context.Context, id int64, status bool) error
|
type OtpStore interface {
|
||||||
// DeleteUser(ctx context.Context, id int64) error
|
ResendOtp(
|
||||||
// CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error)
|
ctx context.Context,
|
||||||
// GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
|
userName string,
|
||||||
// GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
|
) error
|
||||||
// SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
|
CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||||
// UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
|
GetOtp(ctx context.Context, userName string) (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 {
|
|
||||||
// CreateOtp(ctx context.Context, otp domain.Otp) error
|
|
||||||
// GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,51 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error) { // email,phone,error
|
func (s *Service) VerifyOtp(ctx context.Context, userName string, otpCode string) error {
|
||||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email, companyID)
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider, companyID domain.ValidInt64) error {
|
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
|
var err error
|
||||||
// check if user exists
|
// check if user exists
|
||||||
switch medium {
|
switch medium {
|
||||||
case domain.OtpMediumEmail:
|
case domain.OtpMediumEmail:
|
||||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
|
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "")
|
||||||
case domain.OtpMediumSms:
|
case domain.OtpMediumSms:
|
||||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
|
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != domain.ErrUserNotFound {
|
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)
|
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) {
|
||||||
// get otp
|
// 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 sentTo string
|
||||||
|
// var provider domain.Provid
|
||||||
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
||||||
sentTo = registerReq.Email
|
sentTo = registerReq.Email
|
||||||
} else {
|
} else {
|
||||||
sentTo = registerReq.PhoneNumber
|
sentTo = registerReq.PhoneNumber
|
||||||
}
|
}
|
||||||
//
|
|
||||||
otp, err := s.otpStore.GetOtp(
|
// Send OTP to the user (email/SMS)
|
||||||
ctx, sentTo,
|
if err := s.SendOtp(ctx, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil {
|
||||||
domain.OtpRegister, registerReq.OtpMedium)
|
|
||||||
if err != nil {
|
|
||||||
return domain.User{}, err
|
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)
|
// Create the user (no OTP validation yet)
|
||||||
if err != nil {
|
user, err := s.userStore.CreateUserWithoutOtp(ctx, userR)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,15 @@ import (
|
||||||
"time"
|
"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
|
var err error
|
||||||
// check if user exists
|
// check if user exists
|
||||||
switch medium {
|
switch medium {
|
||||||
case domain.OtpMediumEmail:
|
case domain.OtpMediumEmail:
|
||||||
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
|
_, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "")
|
||||||
case domain.OtpMediumSms:
|
case domain.OtpMediumSms:
|
||||||
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
|
_, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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 {
|
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(
|
otp, err := s.otpStore.GetOtp(ctx, resetReq.UserName)
|
||||||
ctx, sentTo,
|
|
||||||
domain.OtpReset, resetReq.OtpMedium)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//
|
|
||||||
|
user, err := s.userStore.GetUserByUserName(ctx, resetReq.UserName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if otp.Used {
|
if otp.Used {
|
||||||
return domain.ErrOtpAlreadyUsed
|
return domain.ErrOtpAlreadyUsed
|
||||||
}
|
}
|
||||||
if time.Now().After(otp.ExpiresAt) {
|
if time.Now().After(otp.ExpiresAt) {
|
||||||
return domain.ErrOtpExpired
|
return domain.ErrOtpExpired
|
||||||
}
|
}
|
||||||
if otp.Otp != resetReq.Otp {
|
if otp.Otp != resetReq.OtpCode {
|
||||||
return domain.ErrInvalidOtp
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,54 @@ package user
|
||||||
import (
|
import (
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"context"
|
"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
|
// Search user
|
||||||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, role, companyID)
|
var roleStr *string
|
||||||
|
if role != nil {
|
||||||
}
|
tmp := strconv.FormatInt(*role, 10)
|
||||||
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
roleStr = &tmp
|
||||||
// update user
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, roleStr)
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *Service) UpdateUser(ctx context.Context, req domain.UpdateUserReq) error {
|
||||||
|
newUser := domain.User{
|
||||||
|
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)
|
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
|
// // 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) {
|
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||||
return s.userStore.GetUserByID(ctx, id)
|
return s.userStore.GetUserByID(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ type CreateAdminReq struct {
|
||||||
Email string `json:"email" example:"john.doe@example.com"`
|
Email string `json:"email" example:"john.doe@example.com"`
|
||||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
Password string `json:"password" example:"password123"`
|
Password string `json:"password" example:"password123"`
|
||||||
OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAdmin godoc
|
// CreateAdmin godoc
|
||||||
|
|
@ -34,7 +33,7 @@ type CreateAdminReq struct {
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/admin [post]
|
// @Router /api/v1/admin [post]
|
||||||
func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
||||||
var OrganizationID domain.ValidInt64
|
// var OrganizationID domain.ValidInt64
|
||||||
var req CreateAdminReq
|
var req CreateAdminReq
|
||||||
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
|
@ -60,36 +59,35 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.OrganizationID == nil {
|
// if req.OrganizationID == nil {
|
||||||
OrganizationID = domain.ValidInt64{
|
// OrganizationID = domain.ValidInt64{
|
||||||
Value: 0,
|
// Value: 0,
|
||||||
Valid: false,
|
// Valid: false,
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// _, err := h.companySvc.GetCompanyByID(c.Context(), *req.OrganizationID)
|
// // _, err := h.companySvc.GetCompanyByID(c.Context(), *req.OrganizationID)
|
||||||
// if err != nil {
|
// // if err != nil {
|
||||||
// h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
|
// // h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
|
||||||
// zap.Int64("status_code", fiber.StatusInternalServerError),
|
// // zap.Int64("status_code", fiber.StatusInternalServerError),
|
||||||
// zap.Int64("company_id", *req.OrganizationID),
|
// // zap.Int64("company_id", *req.OrganizationID),
|
||||||
// zap.Error(err),
|
// // zap.Error(err),
|
||||||
// zap.Time("timestamp", time.Now()),
|
// // zap.Time("timestamp", time.Now()),
|
||||||
// )
|
// // )
|
||||||
// return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
|
// // return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
|
||||||
// }
|
// // }
|
||||||
OrganizationID = domain.ValidInt64{
|
// OrganizationID = domain.ValidInt64{
|
||||||
Value: *req.OrganizationID,
|
// Value: *req.OrganizationID,
|
||||||
Valid: true,
|
// Valid: true,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
user := domain.CreateUserReq{
|
user := domain.CreateUserReq{
|
||||||
FirstName: req.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: req.LastName,
|
LastName: req.LastName,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
PhoneNumber: req.PhoneNumber,
|
PhoneNumber: req.PhoneNumber,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
Role: string(domain.RoleAdmin),
|
Role: string(domain.RoleAdmin),
|
||||||
OrganizationID: OrganizationID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
|
newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
|
||||||
|
|
@ -162,7 +160,6 @@ type AdminRes struct {
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/admin [get]
|
// @Router /api/v1/admin [get]
|
||||||
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||||
|
|
||||||
searchQuery := c.Query("query")
|
searchQuery := c.Query("query")
|
||||||
searchString := domain.ValidString{
|
searchString := domain.ValidString{
|
||||||
Value: searchQuery,
|
Value: searchQuery,
|
||||||
|
|
@ -172,38 +169,32 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||||
createdBeforeQuery := c.Query("created_before")
|
createdBeforeQuery := c.Query("created_before")
|
||||||
var createdBefore domain.ValidTime
|
var createdBefore domain.ValidTime
|
||||||
if createdBeforeQuery != "" {
|
if createdBeforeQuery != "" {
|
||||||
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
|
parsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Info("invalid start_time format", "error", err)
|
h.logger.Info("invalid created_before format", "error", err)
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
|
||||||
}
|
|
||||||
createdBefore = domain.ValidTime{
|
|
||||||
Value: createdBeforeParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
}
|
||||||
|
createdBefore = domain.ValidTime{Value: parsed, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
createdAfterQuery := c.Query("created_after")
|
createdAfterQuery := c.Query("created_after")
|
||||||
var createdAfter domain.ValidTime
|
var createdAfter domain.ValidTime
|
||||||
if createdAfterQuery != "" {
|
if createdAfterQuery != "" {
|
||||||
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
|
parsed, err := time.Parse(time.RFC3339, createdAfterQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Info("invalid start_time format", "error", err)
|
h.logger.Info("invalid created_after format", "error", err)
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
|
||||||
}
|
|
||||||
createdAfter = domain.ValidTime{
|
|
||||||
Value: createdAfterParsed,
|
|
||||||
Valid: true,
|
|
||||||
}
|
}
|
||||||
|
createdAfter = domain.ValidTime{Value: parsed, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
companyFilter := int64(c.QueryInt("company_id"))
|
// companyID := int64(c.QueryInt("company_id"))
|
||||||
filter := domain.UserFilter{
|
filter := domain.UserFilter{
|
||||||
Role: string(domain.RoleAdmin),
|
Role: string(domain.RoleAdmin),
|
||||||
OrganizationID: domain.ValidInt64{
|
// OrganizationID: domain.ValidInt64{
|
||||||
Value: companyFilter,
|
// Value: companyID,
|
||||||
Valid: companyFilter != 0,
|
// Valid: companyID != 0,
|
||||||
},
|
// },
|
||||||
Page: domain.ValidInt{
|
Page: domain.ValidInt{
|
||||||
Value: c.QueryInt("page", 1) - 1,
|
Value: c.QueryInt("page", 1) - 1,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
|
|
@ -217,49 +208,44 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||||
CreatedAfter: createdAfter,
|
CreatedAfter: createdAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
valErrs, ok := h.validator.Validate(c, filter)
|
if valErrs, ok := h.validator.Validate(c, filter); !ok {
|
||||||
if !ok {
|
|
||||||
var errMsg string
|
var errMsg string
|
||||||
for field, msg := range valErrs {
|
for f, msg := range valErrs {
|
||||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
errMsg += fmt.Sprintf("%s: %s; ", f, msg)
|
||||||
}
|
}
|
||||||
h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request",
|
h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request",
|
||||||
zap.Int("status_code", fiber.StatusBadRequest),
|
zap.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Any("validation_errors", valErrs),
|
zap.Any("validation_errors", valErrs),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()))
|
||||||
)
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
|
admins, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
|
||||||
if err != nil {
|
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.Int("status_code", fiber.StatusInternalServerError),
|
||||||
zap.Any("filter", filter),
|
zap.Any("filter", filter),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()))
|
||||||
)
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admins: "+err.Error())
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Admins"+err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]AdminRes, len(admins))
|
result := make([]AdminRes, len(admins))
|
||||||
for index, admin := range admins {
|
for i, admin := range admins {
|
||||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
|
||||||
if err != nil {
|
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
||||||
if err == authentication.ErrRefreshTokenNotFound {
|
h.mongoLoggerSvc.Error("failed to get last login",
|
||||||
lastLogin = &admin.CreatedAt
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||||
} else {
|
zap.Int64("admin_id", admin.ID),
|
||||||
h.mongoLoggerSvc.Error("failed to get last login for admin",
|
zap.Error(err),
|
||||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
zap.Time("timestamp", time.Now()))
|
||||||
zap.Int64("admin_id", admin.ID),
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get last login: "+err.Error())
|
||||||
zap.Error(err),
|
}
|
||||||
zap.Time("timestamp", time.Now()),
|
if err == authentication.ErrRefreshTokenNotFound {
|
||||||
)
|
lastLogin = &admin.CreatedAt
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result[index] = AdminRes{
|
result[i] = AdminRes{
|
||||||
ID: admin.ID,
|
ID: admin.ID,
|
||||||
FirstName: admin.FirstName,
|
FirstName: admin.FirstName,
|
||||||
LastName: admin.LastName,
|
LastName: admin.LastName,
|
||||||
|
|
@ -269,9 +255,6 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||||
EmailVerified: admin.EmailVerified,
|
EmailVerified: admin.EmailVerified,
|
||||||
PhoneVerified: admin.PhoneVerified,
|
PhoneVerified: admin.PhoneVerified,
|
||||||
CreatedAt: admin.CreatedAt,
|
CreatedAt: admin.CreatedAt,
|
||||||
UpdatedAt: admin.UpdatedAt,
|
|
||||||
SuspendedAt: admin.SuspendedAt,
|
|
||||||
Suspended: admin.Suspended,
|
|
||||||
LastLogin: *lastLogin,
|
LastLogin: *lastLogin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -299,38 +282,20 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/admin/{id} [get]
|
// @Router /api/v1/admin/{id} [get]
|
||||||
func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
||||||
userIDstr := c.Params("id")
|
idStr := c.Params("id")
|
||||||
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
if err != nil {
|
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")
|
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 {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("failed to fetch admin by ID",
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get admin: "+err.Error())
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
||||||
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
||||||
h.mongoLoggerSvc.Error("failed to get admin last login",
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get last login: "+err.Error())
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
if err == authentication.ErrRefreshTokenNotFound {
|
if err == authentication.ErrRefreshTokenNotFound {
|
||||||
lastLogin = &user.CreatedAt
|
lastLogin = &user.CreatedAt
|
||||||
|
|
@ -346,18 +311,9 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error {
|
||||||
EmailVerified: user.EmailVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
PhoneVerified: user.PhoneVerified,
|
PhoneVerified: user.PhoneVerified,
|
||||||
CreatedAt: user.CreatedAt,
|
CreatedAt: user.CreatedAt,
|
||||||
UpdatedAt: user.UpdatedAt,
|
|
||||||
SuspendedAt: user.SuspendedAt,
|
|
||||||
Suspended: user.Suspended,
|
|
||||||
LastLogin: *lastLogin,
|
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)
|
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"`
|
FirstName string `json:"first_name" example:"John"`
|
||||||
LastName string `json:"last_name" example:"Doe"`
|
LastName string `json:"last_name" example:"Doe"`
|
||||||
Suspended bool `json:"suspended" example:"false"`
|
Suspended bool `json:"suspended" example:"false"`
|
||||||
OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAdmin godoc
|
// UpdateAdmin godoc
|
||||||
|
|
@ -383,50 +338,25 @@ type updateAdminReq struct {
|
||||||
func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
||||||
var req updateAdminReq
|
var req updateAdminReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - invalid request body",
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||||
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)
|
adminIDStr := c.Params("id")
|
||||||
if !ok {
|
adminID, err := strconv.ParseInt(adminIDStr, 10, 64)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Info("UpdateAdmin failed - invalid Admin ID param",
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid admin ID")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var OrganizationID domain.ValidInt64
|
// var orgID domain.ValidInt64
|
||||||
if req.OrganizationID != nil {
|
// if req.OrganizationID != nil {
|
||||||
OrganizationID = domain.ValidInt64{
|
// orgID = domain.ValidInt64{
|
||||||
Value: *req.OrganizationID,
|
// Value: *req.OrganizationID,
|
||||||
Valid: true,
|
// Valid: true,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
|
||||||
UserID: AdminID,
|
UserID: adminID,
|
||||||
FirstName: domain.ValidString{
|
FirstName: domain.ValidString{
|
||||||
Value: req.FirstName,
|
Value: req.FirstName,
|
||||||
Valid: req.FirstName != "",
|
Valid: req.FirstName != "",
|
||||||
|
|
@ -435,46 +365,11 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
|
||||||
Value: req.LastName,
|
Value: req.LastName,
|
||||||
Valid: req.LastName != "",
|
Valid: req.LastName != "",
|
||||||
},
|
},
|
||||||
Suspended: domain.ValidBool{
|
// OrganizationID: orgID,
|
||||||
Value: req.Suspended,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
OrganizationID: OrganizationID,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error",
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin: "+err.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 {
|
return response.WriteJSON(c, fiber.StatusOK, "Admin updated successfully", nil, 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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,41 +13,40 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loginCustomerReq represents the request body for the LoginCustomer endpoint.
|
// loginUserReq represents the request body for the Loginuser endpoint.
|
||||||
type loginCustomerReq struct {
|
type loginUserReq struct {
|
||||||
Email string `json:"email" validate:"required_without=PhoneNumber" example:"john.doe@example.com"`
|
UserName string `json:"user_name" validate:"required" example:"johndoe"`
|
||||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
Password string `json:"password" validate:"required" example:"password123"`
|
||||||
Password string `json:"password" validate:"required" example:"password123"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginCustomerRes represents the response body for the LoginCustomer endpoint.
|
// loginUserRes represents the response body for the Loginuser endpoint.
|
||||||
type loginCustomerRes struct {
|
type loginUserRes struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginCustomer godoc
|
// Loginuser godoc
|
||||||
// @Summary Login customer
|
// @Summary Login user
|
||||||
// @Description Login customer
|
// @Description Login user
|
||||||
// @Tags auth
|
// @Tags auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param login body loginCustomerReq true "Login customer"
|
// @Param login body loginUserReq true "Login user"
|
||||||
// @Success 200 {object} loginCustomerRes
|
// @Success 200 {object} loginUserRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 401 {object} response.APIResponse
|
// @Failure 401 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/{tenant_slug}/customer-login [post]
|
// @Router /api/v1/{tenant_slug}/user-login [post]
|
||||||
func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
||||||
OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
// OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
||||||
if !OrganizationID.Valid {
|
// if !OrganizationID.Valid {
|
||||||
h.BadRequestLogger().Error("invalid company id")
|
// h.BadRequestLogger().Error("invalid company id")
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||||
}
|
// }
|
||||||
var req loginCustomerReq
|
var req loginUserReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
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.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
|
|
@ -63,15 +62,14 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
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 {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
@ -79,8 +77,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||||
case errors.Is(err, authentication.ErrUserSuspended):
|
case errors.Is(err, authentication.ErrUserSuspended):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
@ -96,21 +93,19 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if successRes.Role != domain.RoleStudent {
|
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.Int("status_code", fiber.StatusForbidden),
|
||||||
zap.String("role", string(successRes.Role)),
|
zap.String("role", string(successRes.Role)),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
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(
|
accessToken, err := jwtutil.CreateJwt(
|
||||||
successRes.UserId,
|
successRes.UserId,
|
||||||
successRes.Role,
|
successRes.Role,
|
||||||
successRes.CompanyID,
|
|
||||||
h.jwtConfig.JwtAccessKey,
|
h.jwtConfig.JwtAccessKey,
|
||||||
h.jwtConfig.JwtAccessExpiry,
|
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")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := loginCustomerRes{
|
res := loginUserRes{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: successRes.RfToken,
|
RefreshToken: successRes.RfToken,
|
||||||
Role: string(successRes.Role),
|
Role: string(successRes.Role),
|
||||||
|
|
@ -142,9 +137,8 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// loginAdminReq represents the request body for the LoginAdmin endpoint.
|
// loginAdminReq represents the request body for the LoginAdmin endpoint.
|
||||||
type loginAdminReq struct {
|
type loginAdminReq struct {
|
||||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
UserName string `json:"user_name" validate:"required" example:"adminuser"`
|
||||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
Password string `json:"password" validate:"required" example:"password123"`
|
||||||
Password string `json:"password" validate:"required" example:"password123"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginAdminRes represents the response body for the LoginAdmin endpoint.
|
// loginAdminRes represents the response body for the LoginAdmin endpoint.
|
||||||
|
|
@ -155,8 +149,8 @@ type LoginAdminRes struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginAdmin godoc
|
// LoginAdmin godoc
|
||||||
// @Summary Login customer
|
// @Summary Login user
|
||||||
// @Description Login customer
|
// @Description Login user
|
||||||
// @Tags auth
|
// @Tags auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
|
@ -167,11 +161,6 @@ type LoginAdminRes struct {
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/{tenant_slug}/admin-login [post]
|
// @Router /api/v1/{tenant_slug}/admin-login [post]
|
||||||
func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
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
|
var req loginAdminReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
h.mongoLoggerSvc.Info("Failed to parse LoginAdmin request",
|
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)
|
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 {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||||
zap.Int("status_code", fiber.StatusBadRequest),
|
zap.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
@ -205,8 +193,7 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
|
||||||
case errors.Is(err, authentication.ErrUserSuspended):
|
case errors.Is(err, authentication.ErrUserSuspended):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||||
zap.Int("status_code", fiber.StatusForbidden),
|
zap.Int("status_code", fiber.StatusForbidden),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
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 {
|
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.Int("status_code", fiber.StatusForbidden),
|
||||||
zap.String("role", string(successRes.Role)),
|
zap.String("role", string(successRes.Role)),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
|
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 {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
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")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := loginCustomerRes{
|
res := loginUserRes{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: successRes.RfToken,
|
RefreshToken: successRes.RfToken,
|
||||||
Role: string(successRes.Role),
|
Role: string(successRes.Role),
|
||||||
|
|
@ -291,14 +277,13 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
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 {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
|
||||||
zap.Int("status_code", fiber.StatusBadRequest),
|
zap.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
|
|
@ -306,8 +291,7 @@ func (h *Handler) LoginSuper(c *fiber.Ctx) error {
|
||||||
case errors.Is(err, authentication.ErrUserSuspended):
|
case errors.Is(err, authentication.ErrUserSuspended):
|
||||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||||
zap.Int("status_code", fiber.StatusForbidden),
|
zap.Int("status_code", fiber.StatusForbidden),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
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",
|
h.mongoLoggerSvc.Warn("Login attempt: super-admin login of non-super-admin",
|
||||||
zap.Int("status_code", fiber.StatusForbidden),
|
zap.Int("status_code", fiber.StatusForbidden),
|
||||||
zap.String("role", string(successRes.Role)),
|
zap.String("role", string(successRes.Role)),
|
||||||
zap.String("email", req.Email),
|
zap.String("user_name", req.UserName),
|
||||||
zap.String("phone", req.PhoneNumber),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
|
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 {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("Failed to create access token",
|
h.mongoLoggerSvc.Error("Failed to create access token",
|
||||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
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")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := loginCustomerRes{
|
res := loginUserRes{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: successRes.RfToken,
|
RefreshToken: successRes.RfToken,
|
||||||
Role: string(successRes.Role),
|
Role: string(successRes.Role),
|
||||||
|
|
@ -373,14 +356,14 @@ type refreshToken struct {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param refresh body refreshToken true "tokens"
|
// @Param refresh body refreshToken true "tokens"
|
||||||
// @Success 200 {object} loginCustomerRes
|
// @Success 200 {object} loginUserRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 401 {object} response.APIResponse
|
// @Failure 401 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/auth/refresh [post]
|
// @Router /api/v1/auth/refresh [post]
|
||||||
func (h *Handler) RefreshToken(c *fiber.Ctx) error {
|
func (h *Handler) RefreshToken(c *fiber.Ctx) error {
|
||||||
|
|
||||||
type loginCustomerRes struct {
|
type loginUserRes struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
Role string `json:"role"`
|
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())
|
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 {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("Failed to create new access token",
|
h.mongoLoggerSvc.Error("Failed to create new access token",
|
||||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
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())
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token:"+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
res := loginCustomerRes{
|
res := loginUserRes{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: req.RefreshToken,
|
RefreshToken: req.RefreshToken,
|
||||||
Role: string(user.Role),
|
Role: string(user.Role),
|
||||||
|
|
@ -482,22 +465,22 @@ type logoutReq struct {
|
||||||
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
|
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogOutCustomer godoc
|
// LogOutuser godoc
|
||||||
// @Summary Logout customer
|
// @Summary Logout user
|
||||||
// @Description Logout customer
|
// @Description Logout user
|
||||||
// @Tags auth
|
// @Tags auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param logout body logoutReq true "Logout customer"
|
// @Param logout body logoutReq true "Logout user"
|
||||||
// @Success 200 {object} response.APIResponse
|
// @Success 200 {object} response.APIResponse
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 401 {object} response.APIResponse
|
// @Failure 401 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /api/v1/auth/logout [post]
|
// @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
|
var req logoutReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
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.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
|
|
@ -512,7 +495,7 @@ func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
|
||||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.mongoLoggerSvc.Info("LogOutCustomer validation failed",
|
h.mongoLoggerSvc.Info("LogOutuser validation failed",
|
||||||
zap.String("errMsg", errMsg),
|
zap.String("errMsg", errMsg),
|
||||||
zap.Int("status_code", fiber.StatusBadRequest),
|
zap.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Any("validation_errors", valErrs),
|
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"
|
"Yimaru-Backend/internal/web_server/ws"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"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")
|
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 {
|
if err != nil {
|
||||||
h.mongoLoggerSvc.Error("Failed to get user",
|
h.mongoLoggerSvc.Error("Failed to get user",
|
||||||
zap.Int64("userID", userID),
|
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")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.OrganizationID.Valid || user.OrganizationID.Value != companyID.Value {
|
// if !user.OrganizationID.Valid || user.OrganizationID.Value != companyID.Value {
|
||||||
h.mongoLoggerSvc.Warn("User attempt to login to different company",
|
// h.mongoLoggerSvc.Warn("User attempt to login to different company",
|
||||||
zap.Int64("userID", userID),
|
// zap.Int64("userID", userID),
|
||||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||||
zap.Error(err),
|
// zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
// zap.Time("timestamp", time.Now()),
|
||||||
)
|
// )
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
|
// return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// referrals, err := h.referralSvc.GetReferralCodesByUser(c.Context(), user.ID)
|
// referrals, err := h.referralSvc.GetReferralCodesByUser(c.Context(), user.ID)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -333,9 +333,8 @@ func (h *Handler) GetTransactionApproverByID(c *fiber.Ctx) error {
|
||||||
EmailVerified: user.EmailVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
PhoneVerified: user.PhoneVerified,
|
PhoneVerified: user.PhoneVerified,
|
||||||
CreatedAt: user.CreatedAt,
|
CreatedAt: user.CreatedAt,
|
||||||
UpdatedAt: user.UpdatedAt,
|
// SuspendedAt: user.SuspendedAt,
|
||||||
SuspendedAt: user.SuspendedAt,
|
// Suspended: user.Suspended,
|
||||||
Suspended: user.Suspended,
|
|
||||||
LastLogin: *lastLogin,
|
LastLogin: *lastLogin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,21 +368,20 @@ type updateTransactionApproverReq struct {
|
||||||
func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
||||||
var req updateTransactionApproverReq
|
var req updateTransactionApproverReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
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.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
)
|
)
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
valErrs, ok := h.validator.Validate(c, req)
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||||
if !ok {
|
|
||||||
var errMsg string
|
var errMsg string
|
||||||
for field, msg := range valErrs {
|
for field, msg := range valErrs {
|
||||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
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.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.Any("validation_errors", valErrs),
|
zap.Any("validation_errors", valErrs),
|
||||||
zap.Time("timestamp", time.Now()),
|
zap.Time("timestamp", time.Now()),
|
||||||
|
|
@ -391,20 +389,20 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
ApproverIDStr := c.Params("id")
|
approverIDStr := c.Params("id")
|
||||||
ApproverID, err := strconv.ParseInt(ApproverIDStr, 10, 64)
|
approverID, err := strconv.ParseInt(approverIDStr, 10, 64)
|
||||||
if err != nil {
|
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.Int("status_code", fiber.StatusBadRequest),
|
||||||
zap.String("admin_id_param", ApproverIDStr),
|
zap.String("approver_id_param", approverIDStr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
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{
|
updateReq := domain.UpdateUserReq{
|
||||||
UserID: ApproverID,
|
UserID: approverID,
|
||||||
FirstName: domain.ValidString{
|
FirstName: domain.ValidString{
|
||||||
Value: req.FirstName,
|
Value: req.FirstName,
|
||||||
Valid: req.FirstName != "",
|
Valid: req.FirstName != "",
|
||||||
|
|
@ -413,26 +411,25 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
|
||||||
Value: req.LastName,
|
Value: req.LastName,
|
||||||
Valid: req.LastName != "",
|
Valid: req.LastName != "",
|
||||||
},
|
},
|
||||||
Suspended: domain.ValidBool{
|
}
|
||||||
Value: req.Suspended,
|
|
||||||
Valid: true,
|
err = h.userSvc.UpdateUser(c.Context(), updateReq)
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
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.Int("status_code", fiber.StatusInternalServerError),
|
||||||
zap.Int64("admin_id", ApproverID),
|
zap.Int64("approver_id", approverID),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Time("timestamp", time.Now()),
|
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.Int("status_code", fiber.StatusOK),
|
||||||
zap.Int64("admin_id", ApproverID),
|
zap.Int64("approver_id", approverID),
|
||||||
zap.Time("timestamp", time.Now()),
|
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
|
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{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: "yimaru.com",
|
Issuer: "yimaru.com",
|
||||||
|
|
@ -39,10 +39,10 @@ func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key
|
||||||
},
|
},
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Role: Role,
|
Role: Role,
|
||||||
CompanyID: domain.NullJwtInt64{
|
// CompanyID: domain.NullJwtInt64{
|
||||||
Value: CompanyID.Value,
|
// Value: CompanyID.Value,
|
||||||
Valid: CompanyID.Valid,
|
// Valid: CompanyID.Valid,
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
jwtToken, err := token.SignedString([]byte(key))
|
jwtToken, err := token.SignedString([]byte(key))
|
||||||
return jwtToken, err
|
return jwtToken, err
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,11 @@ func (a *App) initAppRoutes() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Auth Routes
|
// Auth Routes
|
||||||
tenant.Post("/auth/customer-login", h.LoginCustomer)
|
tenant.Post("/auth/customer-login", h.LoginUser)
|
||||||
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
||||||
groupV1.Post("/auth/super-login", h.LoginSuper)
|
groupV1.Post("/auth/super-login", h.LoginSuper)
|
||||||
groupV1.Post("/auth/refresh", h.RefreshToken)
|
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 {
|
groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("user_id").(int64)
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -122,8 +122,12 @@ func (a *App) initAppRoutes() {
|
||||||
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
||||||
|
|
||||||
// User Routes
|
// 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/resetPassword", h.ResetPassword)
|
||||||
groupV1.Post("/user/sendResetCode", h.SendResetCode)
|
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/resetPassword", h.ResetTenantPassword)
|
||||||
tenant.Post("/user/sendResetCode", h.SendTenantResetCode)
|
tenant.Post("/user/sendResetCode", h.SendTenantResetCode)
|
||||||
|
|
@ -133,10 +137,9 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
|
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.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.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
|
||||||
groupV1.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
|
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.Post("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateTransactionApprover)
|
||||||
// groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover)
|
// groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover)
|
||||||
|
|
||||||
|
|
||||||
//mongoDB logs
|
//mongoDB logs
|
||||||
groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background()))
|
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)
|
// groupV1.Put("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.UpdateTransactionVerified)
|
||||||
|
|
||||||
// Notification Routes
|
// Notification Routes
|
||||||
|
groupV1.Post("/sendSMS", h.SendSingleAfroSMS)
|
||||||
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
||||||
groupV1.Get("/notifications", a.authMiddleware, h.GetUserNotification)
|
groupV1.Get("/notifications", a.authMiddleware, h.GetUserNotification)
|
||||||
groupV1.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
|
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