Compare commits

...

20 Commits

Author SHA1 Message Date
a6c2e7fc7e add: role db 2025-10-08 11:37:40 +07:00
45968832f0 add: device.db 2025-10-08 11:37:33 +07:00
cf37732ebe add: brand.db.js 2025-10-08 11:37:19 +07:00
20b70edaa6 add: user validation 2025-10-08 11:36:56 +07:00
b16e65463d add: is_approve null, validate is_approve login 2025-10-08 11:36:39 +07:00
58cb0c8425 update: user service 2025-10-08 11:35:50 +07:00
0ae39aa504 update: user controller 2025-10-08 11:35:38 +07:00
9ad16fcff7 fix: user route 2025-10-08 11:35:15 +07:00
453b5eb5af update: userdb 2025-10-08 11:34:27 +07:00
76c5eef2f7 fix: req.user, delete verifyrefreshtoken 2025-10-08 11:30:48 +07:00
f9d3bd913f fix: is_approve validations 2025-10-07 23:12:49 +07:00
4b60f922ee fix: auth 2025-10-07 15:45:14 +07:00
a4ef76e74e add: is approve validation 2025-10-07 15:44:48 +07:00
33e70721d9 update verifyacces 2025-10-07 15:15:00 +07:00
8fca2d3cd2 fix: update device 2025-10-07 15:14:44 +07:00
a632791a4d delete 2025-10-07 15:12:54 +07:00
2eec70b7e3 add verify role 2025-10-07 15:12:49 +07:00
ba7f746433 update: device db 2025-10-07 15:12:40 +07:00
ddf9784213 update: auth 2025-10-07 15:12:27 +07:00
48cb3af91d add verifyAccess 2025-10-07 13:48:31 +07:00
16 changed files with 637 additions and 498 deletions

View File

@@ -26,16 +26,23 @@ class AuthController {
const { user, tokens } = await AuthService.register(value);
// Set refresh token in cookie
// Set refresh token di cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: false, //masih dev
secure: false, // masih dev
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari
});
return res.status(201).json(
setResponse({ user, accessToken: tokens.accessToken }, 'User registered successfully', 201)
setResponse(
{
user: { ...user, approved: false },
accessToken: tokens.accessToken
},
'User registered successfully. Waiting for admin approval.',
201
)
);
} catch (err) {
return res.status(err.statusCode || 500).json(
@@ -68,7 +75,7 @@ class AuthController {
const { user, tokens } = await AuthService.login({ email, password });
// Set refresh token in cookie
// Set refresh token di cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: false, // masih dev
@@ -77,7 +84,14 @@ class AuthController {
});
return res.status(200).json(
setResponse({ user, accessToken: tokens.accessToken }, 'Login successful', 200)
setResponse(
{
user: { ...user, approved: true },
accessToken: tokens.accessToken
},
'Login successful',
200
)
);
} catch (err) {
return res.status(err.statusCode || 500).json(

View File

@@ -1,172 +1,157 @@
const userService = require("../services/user.service");
const { ErrorHandler } = require("../helpers/error");
const { hashPassword } = require("../helpers/hashPassword");
const { setResponse, setPaging, setResponsePaging } = require("../helpers/utils");
const { setResponse } = require("../helpers/utils");
const Joi = require("joi");
const { userSchema } = require("../helpers/validation");
// Definisikan skema validasi
const validateTerm = Joi.object({
user_fullname: Joi.string().max(255).required(),
user_name: Joi.string().max(255).required(),
user_email: Joi.string().max(255).email().allow(null),
user_password: Joi.string().max(255).required(),
role_id: Joi.number().integer().allow(null),
is_active: Joi.boolean().required()
});
const getAllUsers = async (req, res) => {
const {
page = 1,
limit = 10,
fullname: userFullname,
username: userName,
is_active: isActive,
criteria,
tenantID,
} = req.query
const offset = (page - 1) * limit;
const filterQuery = {
fixed: {
limit, offset, tenantID
},
filterQuery: [
{
type: 'string',
column: 'user_fullname',
param: userFullname
},
{
type: 'string',
column: 'user_name',
param: userName
},
{
type: 'number',
column: 'is_active',
param: isActive
}
],
filterCriteria:
{
criteria,
column: [
'user_fullname', 'user_name'
]
class UserController {
// Get all users
static async getAllUsers(req, res) {
try {
const users = await userService.getAllUsers();
return res.status(200).json(setResponse(users, "Users retrieved successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
const results = await userService.getAllUsers(filterQuery)
const response = await setResponsePaging(results.data, results.total, parseInt(limit), parseInt(page))
res.status(response.statusCode).json(response)
};
const getAllStatusUsers = async (req, res) => {
const results = await userService.getAllStatusUsers();
const response = await setResponse(results)
res.status(response.statusCode).json(response);
};
const createUser = async (req, res) => {
// Lakukan validasi
const { error } = validateTerm.validate(req.body, { stripUnknown: true });
if (error) {
const response = await setResponse([], error.details[0].message, 400)
return res.status(response.statusCode).json(response);
// Get user by ID
static async getUserById(req, res) {
try {
const { id } = req.params;
const user = await userService.getUserById(id);
return res.status(200).json(setResponse(user, "User retrieved successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
const results = await userService.createUser({
userFullname: req.body.user_fullname,
userName: req.body.user_name,
userEmail: req.body.user_email,
userPassword: req.body.user_password,
roleId: req.body.role_id,
isActive: req.body.is_active, // default 1 jika tidak dikirim
userID: req.body.userID,
tenantID: req.body.tenantID
});
// Create new user
static async createUser(req, res) {
try {
const { error, value } = userSchema.validate(req.body, { abortEarly: false });
const response = await setResponse(results);
if (error) {
const validationErrors = error.details.map((err) => err.message);
throw new ErrorHandler(400, validationErrors);
}
res.status(response.statusCode).json(response);
};
// Kirim approved_by dari user yang bikin
const result = await userService.createUser({
...value,
approved_by: req.user.user_id
});
const getUserById = async (req, res) => {
const { id } = req.params;
const results = await userService.getUserById(id);
const response = await setResponse(results)
res.status(response.statusCode).json(response);
};
const getUserProfile = async (req, res) => {
const { id } = req.user;
const results = await userService.getUserById(id);
const response = await setResponse(results)
res.status(response.statusCode).json(response);
};
const updateUser = async (req, res) => {
const { id } = req.params;
// Lakukan validasi
const { error } = validateTerm.validate(req.body, { stripUnknown: true });
if (error) {
const response = await setResponse([], error.details[0].message, 400)
return res.status(response.statusCode).json(response);
return res.status(201).json(setResponse(result, "User created successfully", 201));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
const results = await userService.updateUser({
userFullname: req.body.user_fullname,
userName: req.body.user_name,
userEmail: req.body.user_email,
userPassword: req.body.user_password,
roleId: req.body.role_id,
isActive: req.body.is_active, // default 1 jika tidak dikirim
userID: req.body.userID,
tenantID: req.body.tenantID,
id
});
// Update user
static async updateUser(req, res) {
try {
const { id } = req.params;
const {
fullname,
name,
email,
phone,
role_id,
is_sa,
is_active,
is_approve
} = req.body;
const response = await setResponse(results);
const result = await userService.updateUser({
user_id: parseInt(id, 10),
fullname,
name,
email,
phone,
role_id,
is_sa,
is_active,
is_approve
});
res.status(response.statusCode).json(response);
};
console.log("PARAM ID:", req.params);
console.log("BODY:", req.body);
const deleteUser = async (req, res) => {
const { id } = req.params;
const userID = req.userID
return res.status(200).json(setResponse(result, "User updated successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
const results = await userService.deleteUser(id, userID);
const response = await setResponse(results)
// Delete user
static async deleteUser(req, res) {
try {
const { id } = req.params;
const deletedBy = req.user?.user_id;
res.status(response.statusCode).json(response);
};
const result = await userService.deleteUser(id, deletedBy);
return res.status(200).json(setResponse(result, "User deleted successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
const getAllRoles = async (req, res) => {
const results = await userService.getAllRoles(req.body.tenantID);
const response = await setResponse(results)
// Change user password
static async changePassword(req, res) {
try {
const { id } = req.params;
const { new_password } = req.body;
res.status(response.statusCode).json(response);
};
if (!id || !new_password) {
throw new ErrorHandler(400, "user_id and new_password are required");
}
module.exports = {
getAllUsers,
createUser,
getUserById,
updateUser,
deleteUser,
getUserProfile,
getAllRoles,
getAllStatusUsers
};
const result = await userService.changeUserPassword(user_id, new_password);
return res.status(200).json(setResponse(result, "Password changed successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
// Get all status users
static async getAllStatusUsers(req, res) {
try {
const result = await userService.getAllStatusUsers();
return res.status(200).json(setResponse(result, "Status list retrieved successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
// Approve user
static async approveUser(req, res) {
try {
const { id } = req.params;
const approverId = req.user?.user_id || null;
const result = await userService.approveUser(id, approverId);
return res.status(200).json(setResponse(result, "User approved successfully", 200));
} catch (error) {
return res
.status(error.statusCode || 500)
.json(setResponse(null, error.message, error.statusCode || 500));
}
}
}
module.exports = UserController;

View File

@@ -1,24 +1,46 @@
const pool = require("../config");
// Get all brands
const getAllBrandsDb = async () => {
const getAllBrandsDb = async (filters = {}) => {
const { whereConditions, queryParams } = pool.buildFilterQuery([
{ column: "b.brand_name", param: filters.brand_name, type: "string" },
{ column: "b.created_by", param: filters.created_by, type: "number" },
]);
const whereClause = whereConditions.length ? `AND ${whereConditions.join(" AND ")}` : "";
const queryText = `
SELECT *
FROM m_brands
WHERE deleted_at IS NULL
ORDER BY brand_id ASC
SELECT
b.brand_id,
b.brand_name,
b.created_at,
b.updated_at,
b.deleted_at,
b.created_by,
b.updated_by,
b.deleted_by
FROM m_brands b
WHERE b.deleted_at IS NULL ${whereClause}
ORDER BY b.brand_id ASC
`;
const result = await pool.query(queryText);
const result = await pool.query(queryText, queryParams);
return result.recordset;
};
// Get brand by ID
const getBrandByIdDb = async (id) => {
const queryText = `
SELECT *
SELECT
brand_id,
brand_name,
created_at,
updated_at,
deleted_at,
created_by,
updated_by,
deleted_by
FROM m_brands
WHERE brand_id = $1
AND deleted_at IS NULL
WHERE brand_id = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
@@ -27,10 +49,17 @@ const getBrandByIdDb = async (id) => {
// Get brand by name
const getBrandByNameDb = async (name) => {
const queryText = `
SELECT *
SELECT
brand_id,
brand_name,
created_at,
updated_at,
deleted_at,
created_by,
updated_by,
deleted_by
FROM m_brands
WHERE brand_name = $1
AND deleted_at IS NULL
WHERE brand_name = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [name]);
return result.recordset[0];
@@ -38,18 +67,24 @@ const getBrandByNameDb = async (name) => {
// Create brand
const createBrandDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_brands", data);
const result = await pool.query(queryText, values);
const { query, values } = pool.buildDynamicInsert("m_brands", {
...data,
created_at: new Date(),
});
const result = await pool.query(query, values);
const insertedId = result.recordset[0]?.inserted_id;
if (!insertedId) return null;
return getBrandByIdDb(insertedId);
};
// Update brand
const updateBrandDb = async (id, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_brands", data, { brand_id: id });
await pool.query(queryText, values);
const { query, values } = pool.buildDynamicUpdate(
"m_brands",
{ ...data, updated_at: new Date() },
{ brand_id: id }
);
await pool.query(query, values);
return getBrandByIdDb(id);
};
@@ -59,8 +94,7 @@ const softDeleteBrandDb = async (id, deletedBy) => {
UPDATE m_brands
SET deleted_at = GETDATE(),
deleted_by = $1
WHERE brand_id = $2
AND deleted_at IS NULL
WHERE brand_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;

View File

@@ -1,85 +1,69 @@
const pool = require("../config");
// Get all devices
const getAllDevicesDb = async () => {
const getAllDevicesDb = async (searchParams = {}) => {
const { whereConditions, queryParams } = pool.buildFilterQuery([
{ column: "d.device_name", param: searchParams.name, type: "string" },
{ column: "d.device_code", param: searchParams.code, type: "string" },
{ column: "d.device_location", param: searchParams.location, type: "string" },
{ column: "b.brand_name", param: searchParams.brand, type: "string" },
]);
const whereClause = whereConditions.length
? `AND ${whereConditions.join(" AND ")}`
: "";
const queryText = `
SELECT *
FROM m_device
WHERE deleted_at IS NULL
ORDER BY device_id ASC
SELECT d.*, b.brand_name
FROM m_device d
LEFT JOIN m_brands b ON d.brand_id = b.brand_id
WHERE d.deleted_at IS NULL ${whereClause}
ORDER BY d.device_id ASC
`;
const result = await pool.query(queryText);
const result = await pool.query(queryText, queryParams);
return result.recordset;
};
// Search devices by keyword
const searchDevicesDb = async (keyword) => {
const queryText = `
SELECT *
FROM m_device
WHERE deleted_at IS NULL
AND (
device_name LIKE '%' + $1 + '%'
OR device_code LIKE '%' + $1 + '%'
OR device_location LIKE '%' + $1 + '%'
OR ip_address LIKE '%' + $1 + '%'
OR device_description LIKE '%' + $1 + '%'
)
ORDER BY device_id ASC
`;
const result = await pool.query(queryText, [keyword]);
return result.recordset;
};
// Get device by ID
const getDeviceByIdDb = async (id) => {
const queryText = `
SELECT *
FROM m_device
WHERE device_id = $1
AND deleted_at IS NULL
SELECT d.*, b.brand_name
FROM m_device d
LEFT JOIN m_brands b ON d.brand_id = b.brand_id
WHERE d.device_id = $1 AND d.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
// Get device by device_code
const getDeviceByCodeDb = async (code) => {
const queryText = `
SELECT *
FROM m_device
WHERE device_code = $1
AND deleted_at IS NULL
SELECT d.*, b.brand_name
FROM m_device d
LEFT JOIN m_brands b ON d.brand_id = b.brand_id
WHERE d.device_code = $1 AND d.deleted_at IS NULL
`;
const result = await pool.query(queryText, [code]);
return result.recordset[0];
};
// Create device
const createDeviceDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_device", data);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
if (!insertedId) return null;
return getDeviceByIdDb(insertedId);
return insertedId ? await getDeviceByIdDb(insertedId) : null;
};
// Update device
const updateDeviceDb = async (id, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_device", data, { device_id: id });
await pool.query(queryText, values);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getDeviceByIdDb(id);
};
// Soft delete device
const softDeleteDeviceDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_device
SET deleted_at = GETDATE(),
deleted_by = $1
WHERE device_id = $2
AND deleted_at IS NULL
SET deleted_at = GETDATE(), deleted_by = $1
WHERE device_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
@@ -92,5 +76,4 @@ module.exports = {
createDeviceDb,
updateDeviceDb,
softDeleteDeviceDb,
searchDevicesDb,
};

View File

@@ -1,59 +1,91 @@
const pool = require("../config");
// Get all roles
const getAllRolesDb = async () => {
const getAllRolesDb = async (filters = {}) => {
const { whereConditions, queryParams } = pool.buildFilterQuery([
{ column: "r.role_name", param: filters.role_name, type: "string" },
{ column: "r.role_level", param: filters.role_level, type: "number" },
]);
const whereClause = whereConditions.length ? `AND ${whereConditions.join(" AND ")}` : "";
const queryText = `
SELECT *
FROM m_roles
WHERE deleted_at IS NULL
ORDER BY role_id ASC
SELECT
r.role_id,
r.role_name,
r.role_description,
r.role_level,
r.created_at,
r.updated_at,
r.updated_by,
r.deleted_at,
r.deleted_by
FROM m_roles r
WHERE r.deleted_at IS NULL ${whereClause}
ORDER BY r.role_id ASC
`;
const result = await pool.query(queryText);
const result = await pool.query(queryText, queryParams);
return result.recordset;
};
// Get role by ID
const getRoleByIdDb = async (roleId) => {
const getRoleByIdDb = async (id) => {
const queryText = `
SELECT *
SELECT
role_id,
role_name,
role_description,
role_level,
created_at,
updated_at,
updated_by,
deleted_at,
deleted_by
FROM m_roles
WHERE role_id = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [roleId]);
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
// Create role
const createRoleDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_roles", data);
const result = await pool.query(queryText, values);
const { query, values } = pool.buildDynamicInsert("m_roles", {
...data,
created_at: new Date(),
});
const result = await pool.query(query, values);
return result.recordset[0]?.inserted_id || null;
};
// Update role
const updateRoleDb = async (roleId, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_roles", data, { role_id: roleId });
await pool.query(queryText, values);
const updateRoleDb = async (id, data) => {
const { query, values } = pool.buildDynamicUpdate(
"m_roles",
{ ...data, updated_at: new Date() },
{ role_id: id }
);
await pool.query(query, values);
return true;
};
// Soft delete role
const deleteRoleDb = async (roleId, deletedBy) => {
const deleteRoleDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_roles
SET deleted_at = GETDATE(),
deleted_by = $1
WHERE role_id = $2
`;
await pool.query(queryText, [deletedBy, roleId]);
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllRolesDb,
getRoleByIdDb,
createRoleDb,
updateRoleDb,
deleteRoleDb,
};
};

View File

@@ -1,159 +1,129 @@
const pool = require("../config");
const { query, buildFilterQuery, buildDynamicUpdate } = require("../config");
// Get all users
const getAllUsersDb = async (searchParams = {}) => {
const { whereConditions, queryParams } = buildFilterQuery([
{ column: "u.user_fullname", param: searchParams.fullname, type: "string" },
{ column: "u.user_name", param: searchParams.username, type: "string" },
{ column: "u.user_email", param: searchParams.email, type: "string" },
{ column: "r.role_name", param: searchParams.role, type: "string" },
]);
const whereClause = whereConditions.length
? `AND ${whereConditions.join(" AND ")}`
: "";
// Get all users s
const getAllUsersDb = async () => {
const queryText = `
SELECT
u.user_id,
u.user_fullname,
u.user_name,
u.user_email,
u.user_phone,
u.is_active,
u.is_sa,
u.is_approve,
u.approved_by,
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.is_active, u.is_sa, u.is_approve, u.approved_by,
approver.user_fullname AS approved_by_name,
u.approved_at,
u.created_at,
u.updated_at,
u.deleted_at,
u.updated_by,
u.deleted_by,
r.role_id,
r.role_name,
r.role_description,
r.role_level
u.approved_at, u.created_at, u.updated_at, u.deleted_at,
u.updated_by, u.deleted_by,
r.role_id, r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
LEFT JOIN m_users approver ON u.approved_by = approver.user_id
WHERE u.deleted_at IS NULL
WHERE u.deleted_at IS NULL ${whereClause}
ORDER BY u.user_id ASC
`;
const result = await pool.query(queryText);
const result = await query(queryText, queryParams);
return result.recordset;
};
// Get user by ID
// Get user by ID
const getUserByIdDb = async (id) => {
const queryText = `
SELECT
u.user_id,
u.user_fullname,
u.user_name,
u.user_email,
u.user_phone,
u.is_active,
u.is_sa,
u.is_approve,
u.approved_by,
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.is_active, u.is_sa, u.is_approve, u.approved_by,
approver.user_fullname AS approved_by_name,
u.approved_at,
u.created_at,
u.updated_at,
u.deleted_at,
u.updated_by,
u.deleted_by,
r.role_id,
r.role_name,
r.role_description,
r.role_level
u.approved_at, u.created_at, u.updated_at, u.deleted_at,
u.updated_by, u.deleted_by,
r.role_id, r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
LEFT JOIN m_users approver ON u.approved_by = approver.user_id
WHERE u.user_id = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
const result = await query(queryText, [id]);
return result.recordset[0];
};
// Get user by email
// Get user by email
const getUserByUserEmailDb = async (email) => {
const queryText = `
SELECT
u.user_id,
u.user_fullname,
u.user_name,
u.user_email,
u.user_phone,
u.user_password,
u.is_active,
u.is_sa,
u.is_approve,
u.role_id,
r.role_name,
r.role_description,
r.role_level
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.user_password, u.is_active, u.is_sa, u.is_approve, u.role_id,
r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
WHERE u.user_email = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [email]);
const result = await query(queryText, [email]);
return result.recordset[0];
};
// Get user by username
// Get user by username
const getUserByUsernameDb = async (username) => {
const queryText = `
SELECT
u.user_id,
u.user_fullname,
u.user_name,
u.user_email,
u.user_phone,
u.user_password,
u.is_active,
u.is_sa,
u.is_approve,
u.role_id,
r.role_name,
r.role_description,
r.role_level
u.user_id, u.user_fullname, u.user_name, u.user_email, u.user_phone,
u.user_password, u.is_active, u.is_sa, u.is_approve, u.role_id,
r.role_name, r.role_description, r.role_level
FROM m_users u
LEFT JOIN m_roles r ON u.role_id = r.role_id
WHERE u.user_name = $1 AND u.deleted_at IS NULL
`;
const result = await pool.query(queryText, [username]);
const result = await query(queryText, [username]);
return result.recordset[0];
};
// Create user
const createUserDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_users", data);
const result = await pool.query(queryText, values);
return result.recordset[0]?.inserted_id || null;
const queryText = `
INSERT INTO m_users
(user_fullname, user_name, user_email, user_phone, user_password, role_id, is_sa, is_active, is_approve, approved_by, approved_at)
VALUES
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
SELECT SCOPE_IDENTITY() as user_id;
`;
const values = [
data.user_fullname,
data.user_name,
data.user_email,
data.user_phone,
data.user_password,
data.role_id || null,
data.is_sa || 0,
data.is_active || 1,
data.is_approve || 0,
data.approved_by || null,
data.approved_at || null
];
const result = await query(queryText, values);
return result.recordset[0]?.user_id || null;
};
// Update user
const updateUserDb = async (userId, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_users", data, { user_id: userId });
await pool.query(queryText, values);
const { query: queryText, values } = buildDynamicUpdate("m_users", data, { user_id: userId });
const finalQuery = queryText.replace("WHERE", "WHERE deleted_at IS NULL AND");
await query(finalQuery, values);
return true;
};
// Approve user
const approveUserDb = async (userId, approverId) => {
const queryText = `
UPDATE m_users
SET is_approve = 1,
approved_by = $1,
approved_at = GETDATE(),
updated_at = GETDATE()
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [approverId, userId]);
return true;
};
// Change password
// Change user password
const changeUserPasswordDb = async (userId, newPassword) => {
const queryText = `
UPDATE m_users
SET user_password = $1,
updated_at = GETDATE()
SET user_password = $1, updated_at = GETDATE()
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [newPassword, userId]);
await query(queryText, [newPassword, userId]);
return true;
};
@@ -161,11 +131,15 @@ const changeUserPasswordDb = async (userId, newPassword) => {
const deleteUserDb = async (userId, deletedBy) => {
const queryText = `
UPDATE m_users
SET deleted_at = GETDATE(),
deleted_by = $1
WHERE user_id = $2
SET
deleted_at = GETDATE(),
deleted_by = $1,
is_active = 0
WHERE user_id = $2
AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, userId]);
await query(queryText, [deletedBy, userId]);
return true;
};
@@ -176,7 +150,6 @@ module.exports = {
getUserByUsernameDb,
createUserDb,
updateUserDb,
approveUserDb,
changeUserPasswordDb,
deleteUserDb,
};

View File

@@ -63,9 +63,38 @@ const deviceUpdateSchema = Joi.object({
})
}).min(1);
// ========================
// Users Validation
// ========================
const userSchema = Joi.object({
fullname: Joi.string().min(3).max(100).required(),
name: Joi.string().alphanum().min(3).max(50).required(),
email: Joi.string().email().required(),
phone: Joi.string()
.pattern(/^(?:\+62|0)8\d{7,10}$/)
.required()
.messages({
'string.pattern.base':
'Phone number must be a valid Indonesian number in format +628XXXXXXXXX'
}),
password: Joi.string()
.min(8)
.pattern(/[A-Z]/, 'uppercase letter')
.pattern(/[a-z]/, 'lowercase letter')
.pattern(/\d/, 'number')
.pattern(/[!@#$%^&*(),.?":{}|<>]/, 'special character')
.required()
.messages({
'string.min': 'Password must be at least 8 characters long',
'string.pattern.name': 'Password must contain at least one {#name}'
}),
role_id : Joi.number().integer().min(1)
});
module.exports = {
registerSchema,
loginSchema,
deviceSchema,
deviceUpdateSchema
deviceUpdateSchema,
userSchema,
};

42
middleware/verifyAcces.js Normal file
View File

@@ -0,0 +1,42 @@
const { ErrorHandler } = require("../helpers/error");
const { getUserByIdDb } = require("../db/user.db");
const verifyAccess = (minLevel = 1, allowUnapprovedReadOnly = false) => {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) throw new ErrorHandler(401, "Unauthorized: User not found");
// Super Admin bypass semua
if (user.is_sa) return next();
// Ambil user lengkap dari DB
const fullUser = await getUserByIdDb(user.user_id);
if (!fullUser) throw new ErrorHandler(403, "Forbidden: User not found");
// Jika belum di-approve
if (!fullUser.is_approve) {
// Hanya boleh GET (read-only)
if (req.method !== "GET") {
throw new ErrorHandler(403, "Account not approved — read-only access");
}
if (allowUnapprovedReadOnly) return next();
throw new ErrorHandler(403, "Account not approved");
}
// Cek role level
if (!fullUser.role_level || fullUser.role_level < minLevel) {
throw new ErrorHandler(403, "Forbidden: Insufficient role level");
}
next();
} catch (err) {
next(err);
}
};
};
module.exports = verifyAccess;

View File

@@ -0,0 +1,38 @@
const { ErrorHandler } = require("../helpers/error");
const { getUserByIdDb } = require("../db/user.db");
const verifyAccess = (minLevel = 1, allowUnapprovedReadOnly = false) => {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) throw new ErrorHandler(401, "Unauthorized: User not found");
// Super Admin bypass semua
if (user.is_sa) return next();
const fullUser = await getUserByIdDb(user.user_id);
if (!fullUser) throw new ErrorHandler(403, "Forbidden: User not found");
if (!fullUser.is_approve) {
if (req.method !== "GET") {
throw new ErrorHandler(403, "Account not approved — read-only access");
}
if (allowUnapprovedReadOnly) return next();
throw new ErrorHandler(403, "Account not approved");
}
if (!fullUser.role_level || fullUser.role_level < minLevel) {
throw new ErrorHandler(403, "Forbidden: Insufficient role level");
}
next();
} catch (err) {
next(err);
}
};
};
module.exports = verifyAccess;

View File

@@ -1,28 +0,0 @@
const { ErrorHandler } = require("../helpers/error");
const verifyRole = (allowedRoles) => {
return (req, res, next) => {
try {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Unauthorized: User not found");
}
// Super Admin bypass semua role
if (user.is_sa) {
return next();
}
if (!allowedRoles.includes(user.role_id)) {
throw new ErrorHandler(403, "Forbidden: Access denied");
}
next();
} catch (err) {
next(err);
}
};
};
module.exports = verifyRole;

View File

@@ -26,7 +26,9 @@ function verifyAccessToken(req, res, next) {
}
const decoded = JWTService.verifyToken(token);
setUser(req, decoded);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
@@ -41,23 +43,6 @@ function verifyAccessToken(req, res, next) {
}
}
function verifyRefreshToken(req, res, next) {
try {
const refreshToken = req.cookies?.refreshToken;
if (!refreshToken) {
throw new ErrorHandler(401, 'Refresh Token is required');
}
const decoded = JWTService.verifyRefreshToken(refreshToken);
req.user = decoded;
next();
} catch (error) {
next(new ErrorHandler(401, 'Refresh token is invalid or expired'));
}
}
module.exports = {
verifyAccessToken,
verifyRefreshToken,
verifyAccessToken
};

View File

@@ -1,14 +1,14 @@
const express = require('express');
const DeviceController = require('../controllers/device.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyRole = require("../middleware/verifyRole")
const verifyAccess = require("../middleware/verifyAcces")
const router = express.Router();
router.get('/', verifyToken.verifyAccessToken, DeviceController.getAll);
router.get('/:id', verifyToken.verifyAccessToken, DeviceController.getById);
router.post('/', verifyToken.verifyAccessToken, verifyRole([1]), DeviceController.create);
router.put('/:id', verifyToken.verifyAccessToken, verifyRole([1, 2]), DeviceController.update);
router.delete('/:id', verifyToken.verifyAccessToken, verifyRole([1]), DeviceController.delete);
router.post('/', verifyToken.verifyAccessToken, verifyAccess, DeviceController.create);
router.put('/:id', verifyToken.verifyAccessToken, verifyAccess, DeviceController.update);
router.delete('/:id', verifyToken.verifyAccessToken, verifyAccess, DeviceController.delete);
module.exports = router;

View File

@@ -1,32 +1,30 @@
const {
getAllUsers,
createUser,
deleteUser,
getUserById,
updateUser,
getUserProfile,
getAllRoles,
getAllStatusUsers
} = require("../controllers/users.controller");
const router = require("express").Router();
const verifyAdmin = require("../middleware/verifyRole");
const verifyToken = require("../middleware/verifyToken");
const express = require('express');
const UserController = require('../controllers/users.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAcces');
router.get("/roles", getAllRoles);
const router = express.Router();
router.get('/profile', verifyToken.verifyAccessToken, getUserProfile);
// Get all users
router.get('/', verifyToken.verifyAccessToken, UserController.getAllUsers);
router.route("/")
.get(verifyToken.verifyAccessToken, getAllUsers)
.post(verifyToken.verifyAccessToken, createUser);
// Get user by ID
router.get('/:id', verifyToken.verifyAccessToken, UserController.getUserById);
router
.route("/status")
.get(verifyToken.verifyAccessToken, getAllStatusUsers);
// Create new user
router.post('/', verifyToken.verifyAccessToken, verifyAccess(), UserController.createUser);
// Update user
router.put('/:id', verifyToken.verifyAccessToken, verifyAccess(), UserController.updateUser);
// Delete user
router.delete('/:id', verifyToken.verifyAccessToken, verifyAccess(), UserController.deleteUser);
// Change user password
router.put('/change-password/:id', verifyToken.verifyAccessToken, verifyAccess(), UserController.changePassword);
// Approve user
router.put('/:id/approve', verifyToken.verifyAccessToken, verifyAccess(), UserController.approveUser);
router.route("/:id")
.get(verifyToken.verifyAccessToken, getUserById)
.put(verifyToken.verifyAccessToken, updateUser)
.delete(verifyToken.verifyAccessToken, deleteUser);
module.exports = router;

View File

@@ -23,9 +23,12 @@ class AuthService {
user_email: email,
user_phone: phone,
user_password: hashedPassword,
role_id: 3,
role_id: null,
is_sa: 0,
is_active: 1
is_active: 1,
is_approve: 0,
approved_by: null,
approved_at: null
});
const newUser = {
@@ -33,8 +36,7 @@ class AuthService {
user_fullname: fullname,
user_name: name,
user_email: email,
user_phone: phone,
role_id: 3,
user_phone: phone
};
// generate token pair
@@ -59,6 +61,10 @@ class AuthService {
throw new ErrorHandler(403, 'User is inactive');
}
if (!user.is_approve) {
throw new ErrorHandler(403, 'Your account has not been approved by admin yet.');
}
const payload = {
user_id: user.user_id,
user_fullname: user.user_fullname,

View File

@@ -28,9 +28,7 @@ class DeviceService {
// Get device by code
static async getDeviceByCode(code) {
const device = await getDeviceByCodeDb(code);
if (!device) {
throw new ErrorHandler(404, 'Device not found');
}
if (!device) throw new ErrorHandler(404, 'Device not found');
return device;
}
@@ -40,7 +38,7 @@ class DeviceService {
data.created_by = userId;
// cek kode unik
// Cek kode unik
const existingDevice = await getDeviceByCodeDb(data.device_code);
if (existingDevice) {
throw new ErrorHandler(400, 'Device code already exists');
@@ -61,14 +59,12 @@ class DeviceService {
data.updated_by = userId;
const updatedDevice = await updateDeviceDb(id, data);
const updatedDevice = await updateDeviceDb(id, data);
await updateDeviceDb(id, data);
return {
return {
message: 'Device updated successfully',
data: updatedDevice,
};
data: updatedDevice,
};
}
// Soft delete device

View File

@@ -1,122 +1,174 @@
const {
const {
createUserDb,
changeUserPasswordDb,
getUserByIdDb,
updateUserDb,
deleteUserDb,
getAllUsersDb,
getUserByUsernameDb,
getAllRoleDb
} = require("../db/user.db");
const { ErrorHandler } = require("../helpers/error");
const { convertId } = require("../helpers/utils");
updateUserDb,
deleteUserDb,
changeUserPasswordDb
} = require('../db/user.db');
const { hashPassword } = require('../helpers/hashPassword');
const { ErrorHandler } = require('../helpers/error');
const statusName = [
{
status: true,
status_name: "Aktif"
}, {
status: false,
status_name: "NonAktif"
}
{ status: true, status_name: "Aktif" },
{ status: false, status_name: "NonAktif" }
];
class UserService {
// Get all status users
getAllStatusUsers = async () => {
try {
return statusName;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
getAllUsers = async (param) => {
// Get all users
getAllUsers = async () => {
try {
const results = await getAllUsersDb(param);
const results = await getAllUsersDb();
results.data.map(element => {
element.is_active = element.is_active == 1 ? true : false
element.is_active_name = convertId(statusName, element.is_active, 'status', 'status_name')
results.forEach(user => {
user.is_active = user.is_active == 1;
user.is_active_name = statusName.find(s => s.status === user.is_active)?.status_name;
delete user.user_password; // remove password
});
return results
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
createUser = async (param) => {
try {
const userByUsername = await getUserByUsernameDb(param.userName, param.tenantID);
if (userByUsername) {
throw new ErrorHandler(401, "username taken already");
}
return await createUserDb(param);
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
// Get user by ID
getUserById = async (id) => {
try {
const user = await getUserByIdDb(id);
// user.password = undefined;
user.is_active = user.is_active == 1 ? true : false
if (!user) throw new ErrorHandler(404, "User not found");
user.is_active = user.is_active == 1;
user.is_active_name = statusName.find(s => s.status === user.is_active)?.status_name;
delete user.user_password;
return user;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
changeUserPassword = async (password, email, tenantID) => {
// Create users
createUser = async ({ fullname, name, email, phone, password, role_id = null, is_sa = 0, is_active = 1, approved_by }) => {
try {
return await changeUserPasswordDb(password, email, tenantID);
const existingUser = await getUserByUsernameDb(name);
if (existingUser) throw new ErrorHandler(400, "Username already taken");
const hashedPassword = await hashPassword(password);
const userId = await createUserDb({
user_fullname: fullname,
user_name: name,
user_email: email,
user_phone: phone,
user_password: hashedPassword,
role_id,
is_sa,
is_active,
is_approve: 1,
approved_by,
approved_at: new Date()
});
return {
user_id: userId,
user_fullname: fullname,
user_name: name,
user_email: email,
user_phone: phone,
role_id,
is_sa,
is_active,
is_approve: 1,
approved_by
};
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
updateUser = async (param) => {
const { userName, id } = param;
const errors = {};
// Update user
updateUser = async ({ user_id, fullname, name, email, phone, role_id, is_sa, is_active, is_approve }) => {
try {
const user = await getUserByIdDb(user_id);
if (!user) throw new ErrorHandler(404, "User not found");
const user = await getUserByIdDb(id);
const findUserByUsername = await getUserByUsernameDb(userName, param.tenantID);
const usernameChanged = userName && user.user_name.toLowerCase() !== userName.toLowerCase();
if (usernameChanged && typeof findUserByUsername === "object") {
errors["username"] = "Username is already taken";
// Cek username
if (name && user.user_name.toLowerCase() !== name.toLowerCase()) {
const userByName = await getUserByUsernameDb(name);
if (userByName) throw new ErrorHandler(400, "Username already taken");
}
if (Object.keys(errors).length > 0) {
throw new ErrorHandler(403, errors);
}
const updateData = {
...(fullname && { user_fullname: fullname }),
...(name && { user_name: name }),
...(email && { user_email: email }),
...(phone && { user_phone: phone }),
...(role_id !== undefined && { role_id }),
...(is_sa !== undefined && { is_sa }),
...(is_active !== undefined && { is_active }),
...(is_approve !== undefined && { is_approve })
};
return await updateUserDb(param);
await updateUserDb(user_id, updateData);
const updatedUser = await getUserByIdDb(user_id);
delete updatedUser.user_password;
updatedUser.is_active = updatedUser.is_active == 1;
updatedUser.is_active_name = statusName.find(s => s.status === updatedUser.is_active)?.status_name;
return updatedUser;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
deleteUser = async (id, userID) => {
// Approve user
approveUser = async (userId, approverId) => {
try {
return await deleteUserDb(id, userID);
const updateData = {
is_approve: 1,
approved_by: approverId,
approved_at: new Date()
};
await updateUserDb(userId, updateData);
const updatedUser = await getUserByIdDb(userId);
delete updatedUser.user_password;
return updatedUser;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
getAllRoles = async (tenantID) => {
// Delete user (soft delete)
deleteUser = async (userId, deletedBy) => {
try {
return await getAllRoleDb(tenantID);
await deleteUserDb(userId, deletedBy);
return { message: "User deleted successfully" };
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
// Change password
changeUserPassword = async (userId, newPassword) => {
try {
const hashedPassword = await hashPassword(newPassword);
await changeUserPasswordDb(userId, hashedPassword);
return { message: "Password updated successfully" };
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
};
}