Merge pull request 'wisdom' (#2) from wisdom into main

Reviewed-on: #2
This commit is contained in:
2025-10-20 03:26:32 +00:00
66 changed files with 3754 additions and 565 deletions

View File

@@ -1,18 +1,18 @@
# # SQL DB Connection Colo
# SQL_HOST=117.102.231.130
# SQL DB Connection Colo
SQL_HOST=117.102.231.130
SQL_DATABASE=cod_piu
SQL_USERNAME=sa
SQL_PASSWORD=@R3M4niA.
SQL_PORT=1433
# SQL_HOST=203.153.114.226
# SQL_PORT=1112
# SQL_DATABASE=piu
# SQL_USERNAME=sa
# SQL_PASSWORD=@R3M4niA.
# SQL_PORT=1433
SQL_HOST=203.153.114.226
SQL_PORT=1112
SQL_DATABASE=piu
SQL_USERNAME=sa
SQL_PASSWORD=piu123
# SQL_PASSWORD=piu123
# Application Port - express server listens on this port (default 9000).
PORT=9528
PORT=9530
ENDPOINT_WA=http://203.153.114.226:9529/send
# ENDPOINT_WA=http://localhost:9529/send
ENDPOINT_FE=http://203.153.114.226:9527

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
.vscode
request.http
*.rest
package-lock.json

20
app.js
View File

@@ -8,6 +8,7 @@ const helmet = require("helmet");
const compression = require("compression");
const unknownEndpoint = require("./middleware/unKnownEndpoint");
const { handleError } = require("./helpers/error");
const { checkConnection } = require("./config");
const app = express();
@@ -24,6 +25,25 @@ app.use("/api", routes);
app.get("/", (req, res) =>
res.send("<h1 style='text-align: center'>HAHALO</h1>")
);
app.get("/check-db", async (req, res) => {
try {
const isConnected = await checkConnection();
res.json({
success: isConnected,
message: isConnected
? "Koneksi database OK"
: "Koneksi database gagal",
});
} catch (error) {
res.status(500).json({
success: false,
message: "Terjadi kesalahan saat cek koneksi database",
error: error.message,
});
}
});
app.use(unknownEndpoint);
app.use(handleError);

View File

@@ -28,6 +28,18 @@ const poolPromise = new sql.ConnectionPool(config)
process.exit(1);
});
async function checkConnection() {
try {
const pool = await poolPromise;
await pool.request().query('SELECT 1 AS isConnected');
console.log('🔍 SQL Server terkoneksi dengan baik');
return true;
} catch (error) {
console.error('⚠️ Gagal cek koneksi SQL Server:', error);
return false;
}
}
/**
* Wrapper query (auto konversi $1 → @p1)
*/
@@ -46,6 +58,11 @@ async function query(text, params = []) {
return request.query(sqlText);
}
function isValidDate(dateStr) {
const d = new Date(dateStr);
return !isNaN(d.getTime()); // true kalau valid
}
/**
* Build filter query
*/
@@ -71,10 +88,24 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
queryParams.push(f.param ? 1 : 0);
whereConditions.push(`${f.column} = $${queryParams.length}`);
break;
case 'between':
if (Array.isArray(f.param) && f.param.length === 2) {
const from = f.param[0];
const to = f.param[1];
if (isValidDate(from) && isValidDate(to)) {
queryParams.push(from);
queryParams.push(to);
whereConditions.push(
`${f.column} BETWEEN $${queryParams.length - 1} AND $${queryParams.length}`
);
}
}
break;
}
});
return { whereConditions, queryParams };
return { whereConditions, whereParamAnd: queryParams };
}
/**
@@ -96,13 +127,17 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
? `AND (${orStringConditions.join(" OR ")})`
: "";
return { whereOrConditions: whereClause, whereParam: queryParams };
return { whereOrConditions: whereClause, whereParamOr: queryParams };
}
/**
* Build dynamic UPDATE
*/
function buildDynamicUpdate(table, data, where) {
data.updated_by = data.userId
delete data.userId;
const setParts = [];
const values = [];
let index = 1;
@@ -118,8 +153,8 @@ function buildDynamicUpdate(table, data, where) {
throw new Error("Tidak ada kolom untuk diupdate");
}
// updated_at otomatis pakai GETDATE()
setParts.push(`updated_at = GETDATE()`);
// updated_at otomatis pakai CURRENT_TIMESTAMP
setParts.push(`updated_at = CURRENT_TIMESTAMP`);
const whereParts = [];
for (const [key, value] of Object.entries(where)) {
@@ -140,6 +175,11 @@ function buildDynamicUpdate(table, data, where) {
* Build dynamic INSERT
*/
function buildDynamicInsert(table, data) {
data.created_by = data.userId
data.updated_by = data.userId
delete data.userId;
const columns = [];
const placeholders = [];
const values = [];
@@ -159,7 +199,7 @@ function buildDynamicInsert(table, data) {
// created_at & updated_at otomatis
columns.push("created_at", "updated_at");
placeholders.push("GETDATE()", "GETDATE()");
placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");
const query = `
INSERT INTO ${table} (${columns.join(", ")})
@@ -195,6 +235,7 @@ async function generateKode(prefix, tableName, columnName) {
}
module.exports = {
checkConnection,
query,
buildFilterQuery,
buildStringOrIlike,

View File

@@ -1,26 +1,99 @@
const authService = require("../services/auth.service");
const AuthService = require('../services/auth.service');
const { setResponse, checkValidate } = require('../helpers/utils');
const { registerSchema, loginSchema } = require('../validate/auth.schema');
const { createCaptcha } = require('../utils/captcha');
const loginUser = async (req, res) => {
const { username, password, role, tenant } = req.body;
const { token, refreshToken, user } = await authService.login(
username,
password,
tenant
);
class AuthController {
// Register
static async register(req, res) {
const { error, value } = await checkValidate(registerSchema, req);
res.header("auth-token", token);
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
sameSite: process.env.NODE_ENV === "development" ? true : "none",
secure: process.env.NODE_ENV === "development" ? false : true,
});
res.status(200).json({
token,
refreshToken,
user,
});
};
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
module.exports = {
loginUser,
};
// Format nomor HP Indonesia
if (value.user_phone && value.user_phone.startsWith('0')) {
value.user_phone = '+62' + value.user_phone.slice(1);
}
const results = await AuthService.register(value);
const response = await setResponse(
{
user: { ...results.user, approved: false },
},
'User registered successfully. Waiting for admin approval.'
);
res.status(response.statusCode).json(response);
}
// Login
static async login(req, res) {
const { error, value } = await checkValidate(loginSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await AuthService.login(value);
// Simpan refresh token di cookie
res.cookie('refreshToken', results.tokens.refreshToken, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000
});
const response = await setResponse(
{
user: { ...results.user, approved: true },
accessToken: results.tokens.accessToken
},
'Login successful'
);
res.status(response.statusCode).json(response);
}
// Refresh Token
static async refreshToken(req, res) {
const refreshToken = req.cookies?.refreshToken;
if (!refreshToken) {
return res.status(401).json(setResponse(null, 'Refresh token is required', 401));
}
const results = await AuthService.refreshToken(refreshToken);
const response = await setResponse(results, 'Token refreshed successfully');
res.status(response.statusCode).json(response);
}
// Logout
static async logout(req, res) {
res.clearCookie('refreshToken', {
httpOnly: true,
sameSite: 'none',
secure: true
});
const response = await setResponse(null, 'Logged out successfully');
res.status(response.statusCode).json(response);
}
// Captcha
static async generateCaptcha(req, res) {
const { svg, text } = createCaptcha();
// Tampilkan captcha di header untuk dev
res.setHeader('X-Captcha-Text', text);
const response = await setResponse({ svg, text }, 'Captcha generated');
res.status(response.statusCode).json(response);
}
}
module.exports = AuthController;

View File

@@ -0,0 +1,71 @@
const DeviceService = require('../services/device.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertDeviceSchema, updateDeviceSchema } = require('../validate/device.schema');
class DeviceController {
// Get all devices
static async getAll(req, res) {
const queryParams = req.query;
const results = await DeviceService.getAllDevices(queryParams);
const response = await setResponsePaging(queryParams, results, 'Device found')
res.status(response.statusCode).json(response);
}
// Get device by ID
static async getById(req, res) {
const { id } = req.params;
const results = await DeviceService.getDeviceById(id);
const response = await setResponse(results, 'Device found')
res.status(response.statusCode).json(response);
}
// Create device
static async create(req, res) {
const { error, value } = await checkValidate(insertDeviceSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await DeviceService.createDevice(value);
const response = await setResponse(results, 'Device created successfully')
return res.status(response.statusCode).json(response);
}
// Update device
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateDeviceSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await DeviceService.updateDevice(id, value);
const response = await setResponse(results, 'Device updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete device
static async delete(req, res) {
const { id } = req.params;
const results = await DeviceService.deleteDevice(id, req.user.user_id);
const response = await setResponse(results, 'Device deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = DeviceController;

View File

@@ -0,0 +1,71 @@
const RolesService = require('../services/roles.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateRolesSchema, insertRolesSchema } = require('../validate/roles.schema');
class RolesController {
// Get all Roles
static async getAll(req, res) {
const queryParams = req.query;
const results = await RolesService.getAllRoles(queryParams);
const response = await setResponsePaging(queryParams, results, 'Roles found')
res.status(response.statusCode).json(response);
}
// Get Roles by ID
static async getById(req, res) {
const { id } = req.params;
const results = await RolesService.getRolesById(id);
const response = await setResponse(results, 'Roles found')
res.status(response.statusCode).json(response);
}
// Create Roles
static async create(req, res) {
const { error, value } = await checkValidate(insertRolesSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await RolesService.createRoles(value);
const response = await setResponse(results, 'Roles created successfully')
return res.status(response.statusCode).json(response);
}
// Update Roles
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateRolesSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await RolesService.updateRoles(id, value);
const response = await setResponse(results, 'Roles updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Roles
static async delete(req, res) {
const { id } = req.params;
const results = await RolesService.deleteRoles(id, req.user.user_id);
const response = await setResponse(results, 'Roles deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = RolesController;

View File

@@ -0,0 +1,71 @@
const ScheduleService = require('../services/schedule.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateScheduleSchema, insertScheduleSchema } = require('../validate/schedule.schema');
class ScheduleController {
// Get all Schedule
static async getAll(req, res) {
const queryParams = req.query;
const results = await ScheduleService.getAllSchedule(queryParams);
const response = await setResponsePaging(queryParams, results, 'Schedule found')
res.status(response.statusCode).json(response);
}
// Get Schedule by ID
static async getById(req, res) {
const { id } = req.params;
const results = await ScheduleService.getScheduleById(id);
const response = await setResponse(results, 'Schedule found')
res.status(response.statusCode).json(response);
}
// Create Schedule
static async create(req, res) {
const { error, value } = await checkValidate(insertScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ScheduleService.insertScheduleDb(value);
const response = await setResponse(results, 'Schedule created successfully')
return res.status(response.statusCode).json(response);
}
// Update Schedule
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ScheduleService.updateSchedule(id, value);
const response = await setResponse(results, 'Schedule updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Schedule
static async delete(req, res) {
const { id } = req.params;
const results = await ScheduleService.deleteSchedule(id, req.user.user_id);
const response = await setResponse(results, 'Schedule deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = ScheduleController;

View File

@@ -0,0 +1,71 @@
const ShiftService = require('../services/shift.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { updateShiftSchema, insertShiftSchema } = require('../validate/shift.schema');
class ShiftController {
// Get all Shift
static async getAll(req, res) {
const queryParams = req.query;
const results = await ShiftService.getAllShift(queryParams);
const response = await setResponsePaging(queryParams, results, 'Shift found')
res.status(response.statusCode).json(response);
}
// Get Shift by ID
static async getById(req, res) {
const { id } = req.params;
const results = await ShiftService.getShiftById(id);
const response = await setResponse(results, 'Shift found')
res.status(response.statusCode).json(response);
}
// Create Shift
static async create(req, res) {
const { error, value } = await checkValidate(insertShiftSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ShiftService.createShift(value);
const response = await setResponse(results, 'Shift created successfully')
return res.status(response.statusCode).json(response);
}
// Update Shift
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateShiftSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await ShiftService.updateShift(id, value);
const response = await setResponse(results, 'Shift updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete Shift
static async delete(req, res) {
const { id } = req.params;
const results = await ShiftService.deleteShift(id, req.user.user_id);
const response = await setResponse(results, 'Shift deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = ShiftController;

View File

@@ -0,0 +1,73 @@
const StatusService = require('../services/status.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertStatusSchema, updateStatusSchema } = require('../validate/status.schema');
class StatusController {
// Get all status
static async getAll(req, res) {
const queryParams = req.query;
const results = await StatusService.getAllStatus(queryParams);
const response = await setResponsePaging(queryParams, results, 'Status found');
res.status(response.statusCode).json(response);
}
// Get status by ID
static async getById(req, res) {
const { id } = req.params;
const results = await StatusService.getStatusById(id);
const response = await setResponse(results, 'Status found');
res.status(response.statusCode).json(response);
}
// Create status
static async create(req, res) {
const { error, value } = await checkValidate(insertStatusSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await StatusService.createStatus(value);
const response = await setResponse(results, 'Status created successfully');
return res.status(response.statusCode).json(response);
}
// Update status
static async update(req, res) {
const { id } = req.params;
console.log("REQ BODY:", req.body);
const { error, value } = await checkValidate(updateStatusSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await StatusService.updateStatus(id, value);
const response = await setResponse(results, 'Status updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete status
static async delete(req, res) {
const { id } = req.params;
const results = await StatusService.deleteStatus(id, req.user.user_id);
const response = await setResponse(results, 'Status deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = StatusController;

View File

@@ -0,0 +1,71 @@
const SubSectionService = require('../services/sub_section.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertSubSectionSchema, updateSubSectionSchema } = require('../validate/sub_section.schema');
class SubSectionController {
// Get all sub sections
static async getAll(req, res) {
const queryParams = req.query;
const results = await SubSectionService.getAll(queryParams);
const response = await setResponsePaging(queryParams, results, 'Sub section found');
res.status(response.statusCode).json(response);
}
// Get sub section by ID
static async getById(req, res) {
const { id } = req.params;
const results = await SubSectionService.getById(id);
const response = await setResponse(results, 'Sub section found');
res.status(response.statusCode).json(response);
}
// Create sub section
static async create(req, res) {
const { error, value } = await checkValidate(insertSubSectionSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await SubSectionService.create(value);
const response = await setResponse(results, 'Sub section created successfully');
return res.status(response.statusCode).json(response);
}
// Update sub section
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateSubSectionSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id;
const results = await SubSectionService.update(id, value);
const response = await setResponse(results, 'Sub section updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete sub section
static async delete(req, res) {
const { id } = req.params;
const results = await SubSectionService.delete(id, req.user.user_id);
const response = await setResponse(results, 'Sub section deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = SubSectionController;

View File

@@ -0,0 +1,71 @@
const TagsService = require('../services/tags.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertTagsSchema, updateTagsSchema } = require('../validate/tags.schema');
class TagsController {
// Get all devices
static async getAll(req, res) {
const queryParams = req.query;
const results = await TagsService.getAllTags(queryParams);
const response = await setResponsePaging(queryParams, results, 'Tags found')
res.status(response.statusCode).json(response);
}
// Get device by ID
static async getById(req, res) {
const { id } = req.params;
const results = await TagsService.getTagByID(id);
const response = await setResponse(results, 'Tags found')
res.status(response.statusCode).json(response);
}
// Create device
static async create(req, res) {
const { error, value } = await checkValidate(insertTagsSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await TagsService.createTags(value);
const response = await setResponse(results, 'Tags created successfully')
return res.status(response.statusCode).json(response);
}
// Update device
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateTagsSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await TagsService.updateTags(id, value);
const response = await setResponse(results, 'Tags updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete device
static async delete(req, res) {
const { id } = req.params;
const results = await TagsService.deleteTags(id, req.user.user_id);
const response = await setResponse(results, 'Tags deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = TagsController;

View File

@@ -0,0 +1,71 @@
const UnitService = require('../services/unit.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertUnitSchema, updateUnitSchema } = require('../validate/unit.schema');
class UnitController {
// Get all units
static async getAll(req, res) {
const queryParams = req.query;
const results = await UnitService.getAllUnits(queryParams);
const response = await setResponsePaging(queryParams, results, 'Unit found');
res.status(response.statusCode).json(response);
}
// Get unit by ID
static async getById(req, res) {
const { id } = req.params;
const results = await UnitService.getUnitById(id);
const response = await setResponse(results, 'Unit found');
res.status(response.statusCode).json(response);
}
// Create unit
static async create(req, res) {
const { error, value } = await checkValidate(insertUnitSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.created_by = req.user.user_id;
const results = await UnitService.createUnit(value);
const response = await setResponse(results, 'Unit created successfully');
return res.status(response.statusCode).json(response);
}
// Update unit
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateUnitSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.updated_by = req.user.user_id;
const results = await UnitService.updateUnit(id, value);
const response = await setResponse(results, 'Unit updated successfully');
res.status(response.statusCode).json(response);
}
// Soft delete unit
static async delete(req, res) {
const { id } = req.params;
const results = await UnitService.deleteUnit(id, req.user.user_id);
const response = await setResponse(results, 'Unit deleted successfully');
res.status(response.statusCode).json(response);
}
}
module.exports = UnitController;

View File

@@ -1,172 +1,108 @@
const userService = require("../services/user.service");
const { ErrorHandler } = require("../helpers/error");
const { hashPassword } = require("../helpers/hashPassword");
const { setResponse, setPaging, setResponsePaging } = require("../helpers/utils");
const Joi = require("joi");
const UserService = require("../services/user.service");
const { setResponse, setResponsePaging, checkValidate } = require("../helpers/utils");
const { userSchema, updateUserSchema, newPasswordSchema } = require("../validate/user.schema");
// 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()
});
class UserController {
// Get all users
static async getAll(req, res) {
const queryParams = req.query;
const getAllUsers = async (req, res) => {
const results = await UserService.getAllUsers(queryParams);
const response = await setResponsePaging(queryParams, results, 'Users found');
const {
page = 1,
limit = 10,
fullname: userFullname,
username: userName,
is_active: isActive,
criteria,
tenantID,
} = req.query
res.status(response.statusCode).json(response);
}
const offset = (page - 1) * limit;
// Get user by ID
static async getById(req, res) {
const { id } = req.params;
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'
]
const results = await UserService.getUserById(id);
const response = await setResponse(results, 'User found');
res.status(response.statusCode).json(response);
}
// Create user
static async create(req, res) {
const { error, value } = await checkValidate(userSchema, req);
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.approved_by = req.user.user_id;
const results = await UserService.createUser(value);
const response = await setResponse(results, 'User created successfully');
res.status(response.statusCode).json(response);
}
const results = await userService.getAllUsers(filterQuery)
const response = await setResponsePaging(results.data, results.total, parseInt(limit), parseInt(page))
// Update user
static async update(req, res) {
const { id } = req.params;
res.status(response.statusCode).json(response)
};
const { error, value } = await checkValidate(updateUserSchema, req);
const getAllStatusUsers = async (req, res) => {
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
const results = await userService.getAllStatusUsers();
const response = await setResponse(results)
value.userId = req.user.user_id;
res.status(response.statusCode).json(response);
};
const results = await UserService.updateUser(id, value);
const response = await setResponse(results, 'User updated successfully');
const createUser = async (req, res) => {
res.status(response.statusCode).json(response);
}
// Approve user
static async approve(req, res) {
const { id } = req.params;
const approverId = req.user.user_id;
const updatedUser = await UserService.approveUser(id, approverId);
const response = await setResponse(updatedUser, 'User approved successfully');
// 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);
}
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
});
// Reject user
static async reject(req, res) {
const { id } = req.params;
const approverId = req.user.user_id;
const response = await setResponse(results);
const updatedUser = await UserService.rejectUser(id, approverId);
const response = await setResponse(updatedUser, 'User rejected successfully');
res.status(response.statusCode).json(response);
};
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);
}
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
});
// Soft delete user
static async delete(req, res) {
const { id } = req.params;
const response = await setResponse(results);
const results = await UserService.deleteUser(id, req.user.user_id);
const response = await setResponse(results, 'User deleted successfully');
res.status(response.statusCode).json(response);
};
res.status(response.statusCode).json(response);
}
const deleteUser = async (req, res) => {
const { id } = req.params;
const userID = req.userID
// Change password
static async changePassword(req, res) {
const { id } = req.params;
const { error, value } = await checkValidate(newPasswordSchema, req);
const results = await userService.deleteUser(id, userID);
const response = await setResponse(results)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
res.status(response.statusCode).json(response);
};
const results = await UserService.changeUserPassword(id, value.new_password);
const response = await setResponse(results, 'Password changed successfully');
const getAllRoles = async (req, res) => {
const results = await userService.getAllRoles(req.body.tenantID);
const response = await setResponse(results)
res.status(response.statusCode).json(response);
}
}
res.status(response.statusCode).json(response);
};
module.exports = {
getAllUsers,
createUser,
getUserById,
updateUser,
deleteUser,
getUserProfile,
getAllRoles,
getAllStatusUsers
};
module.exports = UserController;

116
db/brand.db.js Normal file
View File

@@ -0,0 +1,116 @@
const pool = require("../config");
// Get all brands
const getAllBrandsDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["b.brand_name"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "b.brand_name", param: searchParams.name, type: "string" },
{ column: "b.created_by", param: searchParams.created_by, type: "number" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT COUNT(*) OVER() AS total_data, b.*
FROM m_brands b
WHERE b.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY b.brand_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""};
`;
const result = await pool.query(queryText, queryParams);
const total = result?.recordset.length > 0 ? parseInt(result.recordset[0].total_data, 10) : 0;
return { data: result.recordset, total };
};
// Get brand by ID
const getBrandByIdDb = async (id) => {
const queryText = `
SELECT b.*
FROM m_brands b
WHERE b.brand_id = $1 AND b.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Get brand by name
const getBrandByNameDb = async (name) => {
const queryText = `
SELECT b.*
FROM m_brands b
WHERE b.brand_name = $1 AND b.deleted_at IS NULL
`;
const result = await pool.query(queryText, [name]);
return result.recordset[0];
};
// Create brand
const createBrandDb = async (data) => {
const store = {
...data,
created_at: new Date(),
};
const { query: queryText, values } = pool.buildDynamicInsert("m_brands", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getBrandByIdDb(insertedId) : null;
};
// Update brand
const updateBrandDb = async (id, data) => {
const store = {
...data,
updated_at: new Date(),
};
const whereData = {
brand_id: id,
};
const { query: queryText, values } = pool.buildDynamicUpdate("m_brands", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getBrandByIdDb(id);
};
// Soft delete brand
const deleteBrandDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_brands
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE brand_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllBrandsDb,
getBrandByIdDb,
getBrandByNameDb,
createBrandDb,
updateBrandDb,
deleteBrandDb,
};

133
db/device.db.js Normal file
View File

@@ -0,0 +1,133 @@
const pool = require("../config");
// Get all devices
const getAllDevicesDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.device_name",
"a.device_code",
"a.device_location",
"a.ip_address",
"b.brand_name",
],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.device_code", param: searchParams.code, type: "string" },
{ column: "a.device_location", param: searchParams.location, type: "string" },
{ column: "b.brand_name", param: searchParams.brand, type: "string" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.brand_name,
COALESCE(a.device_code, '') + ' - ' + COALESCE(a.device_name, '') AS device_code_name
FROM m_device a
LEFT JOIN m_brands b ON a.brand_id = b.brand_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.device_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''};
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getDeviceByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.brand_name,
COALESCE(a.device_code, '') + ' - ' + COALESCE(a.device_name, '') AS device_code_name
FROM m_device a
LEFT JOIN m_brands b ON a.brand_id = b.brand_id
WHERE a.device_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createDeviceDb = async (data) => {
const newCode = await pool.generateKode("DVC", "m_device", "device_code");
const store = {
...data,
device_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert(
"m_device",
store
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getDeviceByIdDb(insertedId) : null;
};
const updateDeviceDb = async (id, data) => {
const store = {
...data,
};
// Kondisi WHERE
const whereData = {
device_id: id,
};
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_device",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getDeviceByIdDb(id);
};
const deleteDeviceDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_device
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE device_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllDevicesDb,
getDeviceByIdDb,
createDeviceDb,
updateDeviceDb,
deleteDeviceDb,
};

99
db/roles.db.js Normal file
View File

@@ -0,0 +1,99 @@
const pool = require("../config");
const getAllRolesDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.role_name", "a.role_level", "a.role_description"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.role_name", param: searchParams.role_name, type: "string" },
{ column: "a.role_level", param: searchParams.start_time, type: "string" },
{ column: "a.role_description", param: searchParams.role_description, type: "string" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_roles a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.role_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getRolesByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM m_roles a
WHERE a.role_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertRolesDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_roles", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getRolesByIdDb(insertedId) : null;
};
const updateRolesDb = async (id, data) => {
const store = { ...data };
const whereData = { role_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_roles",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getRolesByIdDb(id);
};
const deleteRolesDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_roles
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE role_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllRolesDb,
getRolesByIdDb,
insertRolesDb,
updateRolesDb,
deleteRolesDb,
};

126
db/schedule.db.js Normal file
View File

@@ -0,0 +1,126 @@
const pool = require("../config");
const { formattedDate } = require("../utils/date");
// Get all schedules
const getAllScheduleDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.schedule_date"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[{ column: "a.schedule_date", param: searchParams.name, type: "date" }],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.shift_name,
b.start_time,
b.end_time
FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.schedule_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getScheduleByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.shift_name,
b.start_time,
b.end_time
FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id
WHERE a.schedule_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertScheduleDb = async (store) => {
const nextDays = Number(store.next_day ?? 0); // default 0 kalau tidak diisi
const insertedRecords = [];
for (let i = 0; i <= nextDays; i++) {
const nextDate = new Date(store.schedule_date);
nextDate.setDate(nextDate.getDate() + i);
const formatted = formattedDate(nextDate);
const newStore = {
...store,
schedule_date: formatted,
};
delete newStore.next_day;
const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
if (insertedId) {
const record = await getScheduleByIdDb(insertedId);
insertedRecords.push(record);
}
}
return insertedRecords;
};
const updateScheduleDb = async (id, data) => {
const store = { ...data };
const whereData = { schedule_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"schedule",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getScheduleByIdDb(id);
};
// Soft delete schedule
const deleteScheduleDb = async (id, deletedBy) => {
const queryText = `
UPDATE schedule
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE schedule_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllScheduleDb,
getScheduleByIdDb,
insertScheduleDb,
updateScheduleDb,
deleteScheduleDb,
};

100
db/shift.db.js Normal file
View File

@@ -0,0 +1,100 @@
const pool = require("../config");
const getAllShiftDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.shift_name", "a.start_time", "a.end_time"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.shift_name", param: searchParams.name, type: "string" },
{ column: "a.start_time", param: searchParams.start_time, type: "time" },
{ column: "a.end_time", param: searchParams.end_time, type: "time" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_shift a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.shift_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getShiftByIdDb = async (id) => {
const queryText = `
SELECT
a.*
FROM m_shift a
WHERE a.shift_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertShiftDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("m_shift", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getShiftByIdDb(insertedId) : null;
};
const updateShiftDb = async (id, data) => {
const store = { ...data };
const whereData = { shift_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_shift",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getShiftByIdDb(id);
};
const deleteShiftDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_shift
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE shift_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllShiftDb,
getShiftByIdDb,
insertShiftDb,
updateShiftDb,
deleteShiftDb,
};

117
db/status.db.js Normal file
View File

@@ -0,0 +1,117 @@
const pool = require("../config");
// Get all status
const getAllStatusDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.status_name", "a.status_description"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.status_number", param: searchParams.number, type: "number" },
{ column: "a.is_active", param: searchParams.is_active, type: "boolean" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*
FROM m_status a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.status_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''};
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getStatusByIdDb = async (id) => {
const queryText = `
SELECT *
FROM m_status a
WHERE a.status_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Check if status_number already exists
const checkStatusNumberExistsDb = async (status_number) => {
const queryText = `
SELECT 1
FROM m_status
WHERE status_number = $1 AND deleted_at IS NULL
`;
const result = await pool.query(queryText, [status_number]);
return result.recordset.length > 0;
};
const createStatusDb = async (data) => {
const { query: queryText, values } = pool.buildDynamicInsert(
"m_status",
data
);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getStatusByIdDb(insertedId) : null;
};
const updateStatusDb = async (id, data) => {
const store = { ...data };
const whereData = { status_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_status",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getStatusByIdDb(id);
};
const deleteStatusDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_status
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE status_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllStatusDb,
getStatusByIdDb,
createStatusDb,
updateStatusDb,
deleteStatusDb,
checkStatusNumberExistsDb,
};

108
db/sub_section.db.js Normal file
View File

@@ -0,0 +1,108 @@
const pool = require("../config");
// Get all sub sections
const getAllSubSectionsDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// OR condition (pencarian bebas)
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.sub_section_code", "a.sub_section_name"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ?? queryParams;
// AND condition (filter spesifik)
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.sub_section_code", param: searchParams.code, type: "string" },
{ column: "a.sub_section_name", param: searchParams.name, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ?? queryParams;
// Query utama
const queryText = `
SELECT COUNT(*) OVER() AS total_data, a.*
FROM m_plant_sub_section a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.sub_section_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""};
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get sub section by ID
const getSubSectionByIdDb = async (id) => {
const queryText = `
SELECT a.*
FROM m_plant_sub_section a
WHERE a.sub_section_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Create new sub section
const createSubSectionDb = async (data) => {
// Generate kode otomatis
const newCode = await pool.generateKode("SUB", "m_plant_sub_section", "sub_section_code");
const store = {
...data,
sub_section_code: newCode
};
const { query: queryText, values } = pool.buildDynamicInsert("m_plant_sub_section", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getSubSectionByIdDb(insertedId) : null;
};
// Update sub section
const updateSubSectionDb = async (id, data) => {
const store = { ...data };
const whereData = { sub_section_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate("m_plant_sub_section", store, whereData);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getSubSectionByIdDb(id);
};
// Soft delete sub section
const deleteSubSectionDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_plant_sub_section
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE sub_section_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllSubSectionsDb,
getSubSectionByIdDb,
createSubSectionDb,
updateSubSectionDb,
deleteSubSectionDb,
};

140
db/tags.db.js Normal file
View File

@@ -0,0 +1,140 @@
const pool = require("../config");
// Get all tags
const getAllTagsDb = async (searchParams = {}) => {
let queryParams = [];
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"a.tag_name",
"a.tag_code",
"a.tag_number",
"a.data_type",
"a.unit",
"b.device_name",
"c.sub_section_name",
],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.tag_name", param: searchParams.name, type: "string" },
{ column: "a.tag_code", param: searchParams.code, type: "string" },
{ column: "a.data_type", param: searchParams.data, type: "string" },
{ column: "a.unit", param: searchParams.unit, type: "string" },
{ column: "b.device_name", param: searchParams.device, type: "string" },
{
column: "b.device_code",
param: searchParams.device,
type: "string",
},
{
column: "c.sub_section_name",
param: searchParams.subsection,
type: "string",
},
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.device_name,
b.device_code,
c.sub_section_name
FROM m_tags a
LEFT JOIN m_device b ON a.device_id = b.device_id
LEFT JOIN m_plant_sub_section c ON a.sub_section_id = c.sub_section_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.tag_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getTagsByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.device_name,
b.device_code,
c.sub_section_name
FROM m_tags a
LEFT JOIN m_device b ON a.device_id = b.device_id
LEFT JOIN m_plant_sub_section c ON a.sub_section_id = c.sub_section_id
WHERE a.tag_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
const createTagsDb = async (data) => {
const newCode = await pool.generateKode("TAG", "m_tags", "tag_code");
const store = {
...data,
tag_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert("m_tags", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getTagsByIdDb(insertedId) : null;
};
const updateTagsDb = async (id, data) => {
const store = { ...data };
const whereData = { tag_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_tags",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getTagsByIdDb(id);
};
// Soft delete tag
const deleteTagsDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_tags
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE tag_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllTagsDb,
getTagsByIdDb,
createTagsDb,
updateTagsDb,
deleteTagsDb,
};

119
db/unit.db.js Normal file
View File

@@ -0,0 +1,119 @@
const pool = require("../config");
// Get all units
const getAllUnitsDb = async (searchParams = {}) => {
let queryParams = [];
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.unit_code", "a.unit_name"],
searchParams.criteria,
queryParams
);
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.unit_code", param: searchParams.code, type: "string" },
{ column: "a.unit_name", param: searchParams.name, type: "string" },
{ column: "a.tag_id", param: searchParams.tag, type: "number" },
{ column: "a.is_active", param: searchParams.status, type: "string" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
COALESCE(a.unit_code, '') + ' - ' + COALESCE(a.unit_name, '') AS unit_code_name
FROM m_unit a
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.unit_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""};
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get unit by ID
const getUnitByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
COALESCE(a.unit_code, '') + ' - ' + COALESCE(a.unit_name, '') AS unit_code_name
FROM m_unit a
WHERE a.unit_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset;
};
// Create unit
const createUnitDb = async (data) => {
const newCode = await pool.generateKode("UNT", "m_unit", "unit_code");
const store = {
...data,
unit_code: newCode,
};
const { query: queryText, values } = pool.buildDynamicInsert("m_unit", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getUnitByIdDb(insertedId) : null;
};
// Update unit
const updateUnitDb = async (id, data) => {
const store = { ...data };
const whereData = { unit_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"m_unit",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUnitByIdDb(id);
};
// Soft delete unit
const deleteUnitDb = async (id, deletedBy) => {
const queryText = `
UPDATE m_unit
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE unit_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
// Export
module.exports = {
getAllUnitsDb,
getUnitByIdDb,
createUnitDb,
updateUnitDb,
deleteUnitDb,
};

View File

@@ -1,142 +1,204 @@
const pool = require("../config");
const getAllUsersDb = async (param) => {
// limit & offset masuk fixed param
let fixedParams = [param.fixed.limit, param.fixed.offset, param.fixed.tenantID];
// Get all users
const getAllUsersDb = async (searchParams = {}) => {
let queryParams = [];
const { whereOrConditions, whereParam } = pool.buildStringOrIlike(
param.filterCriteria.column,
param.filterCriteria.criteria,
fixedParams
// Pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
// Search
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
[
"u.user_fullname",
"u.user_name",
"u.user_email",
"r.role_name"
],
searchParams.criteria,
queryParams
);
const { whereConditions, queryParams } = pool.buildFilterQuery(param.filterQuery, whereParam);npm
const query = `
SELECT mut.*, mr.role_name, COUNT(*) OVER() AS total
FROM m_users mut
LEFT JOIN system.role_tenant mr ON mr.role_id = mut.role_id
WHERE mut.deleted_at IS NULL AND mut.is_sa != 1 AND mut.tenant_id = $3
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""}
ORDER BY mut.user_id
OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY
queryParams = whereParamOr ? whereParamOr : queryParams;
// Filter
const { whereConditions, whereParamAnd } = pool.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" },
],
queryParams
);
queryParams = whereParamAnd ? whereParamAnd : queryParams;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
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
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
${whereConditions.length > 0 ? ` AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''}
ORDER BY u.user_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''};
`;
const result = await pool.query(query, queryParams);
const rows = result.recordset;
const result = await pool.query(queryText, queryParams);
const total = rows.length > 0 ? parseInt(rows[0].total, 10) : 0;
return { data: rows, total };
};
const createUserDb = async (param) => {
const insertData = {
tenant_id: param.tenantID,
user_fullname: param.userFullname,
user_name: param.userName,
user_email: param.userEmail ?? null,
user_password: param.userPassword,
role_id: param.roleId ?? null,
is_active: param.isActive ? 1 : 0,
created_by: param.userID,
updated_by: param.userID,
};
const { query, values } = pool.buildDynamicInsert("m_users", insertData);
const result = await pool.query(query, values);
return result.recordset[0];
const total =
result?.recordset.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
// Get user by ID
const getUserByIdDb = async (id) => {
const query = `
SELECT mut.*
FROM m_users mut
WHERE mut.user_id = $1
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,
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
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(query, [id]);
const result = await pool.query(queryText, [id]);
return result.recordset[0];
};
// 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
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]);
return result.recordset[0];
};
// Get user by username
const getUserByUsernameDb = async (username) => {
const query = `
SELECT mut.*
FROM m_users mut
WHERE LOWER(mut.username) = LOWER($1)
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
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(query, [username]);
const result = await pool.query(queryText, [username]);
return result.recordset[0];
};
const getUserByUserEmailDb = async (userEmail) => {
const query = `
SELECT mut.*
FROM m_users mut
WHERE LOWER(mut.user_email) = LOWER($1)
`;
const result = await pool.query(query, [userEmail]);
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);
const insertedId = result.recordset[0]?.inserted_id;
return insertedId ? await getUserByIdDb(insertedId) : null;
};
const updateUserDb = async (param) => {
const updateData = {
tenant_id: param.tenantID,
user_fullname: param.userFullname,
user_name: param.userName,
user_email: param.userEmail ?? null,
user_password: param.userPassword,
role_id: param.roleId ?? null,
is_active: param.isActive ? 1 : 0,
updated_by: param.userID,
};
const whereData = { user_id: param.id };
const { query, values } = pool.buildDynamicUpdate("m_users", updateData, whereData);
const result = await pool.query(query, values);
return result.recordset[0];
// Update user
const updateUserDb = async (userId, data) => {
const { query: queryText, values } = pool.buildDynamicUpdate("m_users", data, {
user_id: userId,
});
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUserByIdDb(userId);
};
const deleteUserDb = async (id, userID) => {
const query = `
UPDATE m_users
SET deleted_at = GETDATE(), deleted_by = $1
WHERE user_id = $2;
SELECT * FROM m_users WHERE user_id = $2
// Approve user
const approveUserDb = async (userId, approverId) => {
const queryText = `
UPDATE m_users
SET
is_approve = 2,
approved_by = $1,
approved_at = CURRENT_TIMESTAMP,
updated_by = $1,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
const result = await pool.query(query, [userID, id]);
return result.recordset[0];
await pool.query(queryText, [approverId, userId]);
return true;
};
const changeUserPasswordDb = async (hashedPassword, userEmail, tenantId) => {
const query = `
UPDATE m_users
SET user_password = $1
WHERE user_email = $2 AND tenant_id = $3
// Reject user
const rejectUserDb = async (userId, approverId) => {
const queryText = `
UPDATE m_users
SET
is_approve = 0,
approved_by = $1,
approved_at = CURRENT_TIMESTAMP,
updated_by = $1,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
return pool.query(query, [hashedPassword, userEmail, tenantId]);
await pool.query(queryText, [approverId, userId]);
return true;
}
// Change user password
const changeUserPasswordDb = async (userId, newPassword) => {
const queryText = `
UPDATE m_users
SET user_password = $1, updated_at = CURRENT_TIMESTAMP
WHERE user_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [newPassword, userId]);
return true;
};
const getAllRoleDb = async (tenantId) => {
const query = `
SELECT *
FROM system.role_tenant
WHERE deleted_at IS NULL AND tenant_id = $1
// Soft delete user
const deleteUserDb = async (userId, deletedBy) => {
const queryText = `
UPDATE m_users
SET
deleted_at = CURRENT_TIMESTAMP,
deleted_by = $1,
is_active = 0
WHERE user_id = $2
AND deleted_at IS NULL
`;
const result = await pool.query(query, [tenantId]);
return result.recordset;
await pool.query(queryText, [deletedBy, userId]);
return true;
};
module.exports = {
getAllUsersDb,
getUserByIdDb,
getUserByUserEmailDb,
updateUserDb,
createUserDb,
deleteUserDb,
getUserByUsernameDb,
createUserDb,
updateUserDb,
approveUserDb,
rejectUserDb,
changeUserPasswordDb,
getAllRoleDb,
deleteUserDb,
};

View File

@@ -1,43 +1,29 @@
const setResponse = async (data = [], message = "success", statusCode = 200) => {
const response = {
data,
total: data.length,
message,
statusCode
}
const setResponse = (data = null, message = "success", statusCode = 200) => {
const total = Array.isArray(data) ? data.length : null;
return response
return {
message,
statusCode,
rows: total,
data,
};
};
const setResponsePaging = async (data = [], total, limit, page, message = "success", statusCode = 200) => {
const setResponsePaging = async (queryParam, data = [], message = "success", statusCode = 200) => {
const totalPages = Math.ceil(total / limit);
const totalPages = Math.ceil(data?.total / Number(queryParam.limit ?? 0));
const response = {
message,
statusCode,
data,
total: data.length,
rows: data?.data?.length,
paging: {
total,
limit,
page,
page_total: totalPages
}
}
return response
};
const setPaging = async (total, limit, page) => {
const totalPages = Math.ceil(total / limit);
const response = {
total,
limit,
page,
page_total: totalPages
current_limit: Number(queryParam.limit ?? 0),
current_page: Number(queryParam.page ?? 0),
total_limit: data?.total,
total_page: totalPages
},
data: data?.data ?? []
}
return response
@@ -86,4 +72,19 @@ function orderByClauseQuery(orderParams) {
return orderByClause
}
module.exports = { setResponse, setResponsePaging, setPaging, convertId, formatToYYYYMMDD, orderByClauseQuery };
const checkValidate = (validateSchema, req) => {
const { error, value } = validateSchema.validate(req.body || {}, { abortEarly: false });
if (error) {
const errors = error.details.reduce((acc, cur) => {
const field = Array.isArray(cur.path) ? cur.path.join('.') : String(cur.path);
if (!acc[field]) acc[field] = [];
acc[field].push(cur.message);
return acc;
}, {});
return { error: errors, value }
}
return { error, value }
}
module.exports = { setResponse, setResponsePaging, convertId, formatToYYYYMMDD, orderByClauseQuery, checkValidate };

View File

@@ -1,9 +0,0 @@
const validateUser = (email, password) => {
const validEmail = typeof email === "string" && email.trim() !== "";
const validPassword =
typeof password === "string" && password.trim().length >= 6;
return validEmail && validPassword;
};
module.exports = validateUser;

View File

@@ -5,6 +5,6 @@ const { logger } = require("./utils/logger");
const server = http.createServer(app);
const PORT = process.env.PORT || 9524;
const PORT = process.env.PORT || 9533;
server.listen(PORT, () => logger.info(`Magic happening on port: ${PORT}`));

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,14 +0,0 @@
const { ErrorHandler } = require("../helpers/error");
module.exports = (req, res, next) => {
const { roles } = req.user;
if (roles && roles.includes("admin")) {
req.user = {
...req.user,
roles,
};
return next();
} else {
throw new ErrorHandler(401, "require admin role");
}
};

View File

@@ -1,47 +1,48 @@
const jwt = require("jsonwebtoken");
const { ErrorHandler } = require("../helpers/error");
const JWTService = require('../utils/jwt');
const { ErrorHandler } = require('../helpers/error');
const verifyToken = (req, res, next) => {
const authHeader = req.header("Authorization");
// console.log("authHeader", authHeader)
// Pastikan header Authorization ada dan berisi token
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new ErrorHandler(401, "Token missing or invalid");
}
// Ambil token dari header Authorization
const token = authHeader.split(" ")[1];
function setUser(req, decoded) {
req.user = {
userId: decoded.user_id,
fullname: decoded.user_fullname,
username: decoded.user_name,
email: decoded.user_email,
roleId: decoded.role_id,
roleName: decoded.role_name,
is_sa: decoded.is_sa
};
}
function verifyAccessToken(req, res, next) {
try {
// const decoded = jwt.decode(token, { complete: true });
// console.log("decoded", decoded)
// console.log("==============================")
// console.log("token", token)
// console.log("process.env.SECRET", process.env.SECRET)
// // console.log("==============================> ", jwt.verify(token, process.env.SECRET))
// jwt.verify(token, process.env.SECRET, (err, decoded) => {
// if (err) {
// console.error('Error verifying token: ==============================>', err.message);
// } else {
// console.log('Decoded payload: ==============================>', decoded);
// }
// });
let token = req.cookies?.accessToken;
if (!token) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer')) {
throw new ErrorHandler(401, 'Access Token is required');
}
token = authHeader.split(' ')[1];
}
const decoded = JWTService.verifyToken(token);
req.user = decoded;
const verified = jwt.verify(token, process.env.SECRET);
req.tokenExtract = verified;
// console.log(req.tokenExtract);
req.userID = req.tokenExtract.user_id
req.tenantID = req.tokenExtract.tenant_id
req.roleID = req.tokenExtract.role_id
req.body.userID = req.tokenExtract.user_id
req.body.tenantID = req.tokenExtract.tenant_id
req.query.tenantID = req.tokenExtract.tenant_id
next();
} catch (error) {
throw new ErrorHandler(401, error.message || "Invalid Token");
}
};
if (error.name === 'TokenExpiredError') {
return next(new ErrorHandler(401, 'Access token expired'));
}
module.exports = verifyToken;
if (error.name === 'JsonWebTokenError') {
return next(new ErrorHandler(401, 'Invalid access token'));
}
return next(new ErrorHandler(500, 'Internal authentication error'));
}
}
module.exports = {
verifyAccessToken
};

View File

@@ -1,8 +0,0 @@
const router = require("express").Router();
const {
loginUser,
} = require("../controllers/auth.controller");
router.post("/login", loginUser);
module.exports = router;

11
routes/auth.route.js Normal file
View File

@@ -0,0 +1,11 @@
const express = require('express');
const AuthController = require("../controllers/auth.controller");
const router = express.Router();
router.post('/login', AuthController.login);
router.post('/register', AuthController.register);
router.get('/generate-captcha', AuthController.generateCaptcha);
router.post('/refresh-token', AuthController.refreshToken);
module.exports = router;

17
routes/device.route.js Normal file
View File

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

View File

@@ -1,8 +1,25 @@
const router = require("express").Router();
const auth = require("./auth");
const users = require("./users");
const auth = require("./auth.route");
const users = require("./users.route");
const device = require('./device.route');
const roles = require('./roles.route');
const tags = require("./tags.route");
const subSection = require("./sub_section.route");
const shift = require("./shift.route");
const schedule = require("./schedule.route");
const status = require("./status.route");
const unit = require("./unit.route")
router.use("/auth", auth);
router.use("/users", users);
router.use("/user", users);
router.use("/device", device);
router.use("/roles", roles);
router.use("/tags", tags);
router.use("/plant-sub-section", subSection);
router.use("/shift", shift);
router.use("/schedule", schedule);
router.use("/status", status);
router.use("/unit", unit);
module.exports = router;

17
routes/roles.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const RolesController = require('../controllers/roles.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, RolesController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), RolesController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, RolesController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), RolesController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), RolesController.delete);
module.exports = router;

17
routes/schedule.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const ScheduleController = require('../controllers/schedule.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, ScheduleController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), ScheduleController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, ScheduleController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), ScheduleController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), ScheduleController.delete);
module.exports = router;

17
routes/shift.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const ShiftController = require('../controllers/shift.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, ShiftController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), ShiftController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, ShiftController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), ShiftController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), ShiftController.delete);
module.exports = router;

17
routes/status.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const StatusController = require('../controllers/status.controller');
const verifyToken = require("../middleware/verifyToken");
const verifyAccess = require("../middleware/verifyAccess");
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, StatusController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), StatusController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, StatusController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), StatusController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), StatusController.delete);
module.exports = router;

View File

@@ -0,0 +1,17 @@
const express = require('express');
const PlantSubSectionController = require('../controllers/sub_section.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, PlantSubSectionController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, PlantSubSectionController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), PlantSubSectionController.delete);
module.exports = router;

17
routes/tags.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const TagsController = require('../controllers/tags.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, TagsController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), TagsController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, TagsController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), TagsController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), TagsController.delete);
module.exports = router;

17
routes/unit.route.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const UnitController = require('../controllers/unit.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, UnitController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), UnitController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, UnitController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), UnitController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), UnitController.delete);
module.exports = router;

View File

@@ -1,33 +0,0 @@
const {
getAllUsers,
createUser,
deleteUser,
getUserById,
updateUser,
getUserProfile,
getAllRoles,
getAllStatusUsers
} = require("../controllers/users.controller");
const router = require("express").Router();
const verifyAdmin = require("../middleware/verifyAdmin");
const verifyToken = require("../middleware/verifyToken");
router.get("/roles", getAllRoles);
router.route("/profile")
.get(getUserProfile);
router.route("/")
.get(verifyToken, getAllUsers)
.post(verifyToken, createUser);
router
.route("/status")
.get(verifyToken, getAllStatusUsers);
router.route("/:id")
.get(verifyToken, getUserById)
.put(verifyToken, updateUser)
.delete(verifyToken, deleteUser);
module.exports = router;

26
routes/users.route.js Normal file
View File

@@ -0,0 +1,26 @@
const express = require('express');
const UserController = require('../controllers/users.controller');
const verifyToken = require('../middleware/verifyToken');
const verifyAccess = require('../middleware/verifyAccess');
const router = express.Router();
router.route('/')
.get(verifyToken.verifyAccessToken, UserController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), UserController.create);
router.route('/:id')
.get(verifyToken.verifyAccessToken, UserController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), UserController.delete);
router.route('/change-password/:id')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.changePassword);
router.route('/:id/approve')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.approve);
router.route('/:id/reject')
.put(verifyToken.verifyAccessToken, verifyAccess(), UserController.reject);
module.exports = router;

View File

@@ -1,77 +1,130 @@
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const validateUser = require("../helpers/validateUser");
const { ErrorHandler } = require("../helpers/error");
const {
getUserByUserEmailDb,
createUserDb,
getUserByUsernameDb
} = require("../db/user.db");
const { logger } = require("../utils/logger");
} = require('../db/user.db');
const { hashPassword, comparePassword } = require('../helpers/hashPassword');
const { ErrorHandler } = require('../helpers/error');
const JWTService = require('../utils/jwt');
class AuthService {
async login(username, password, tenantId) {
// Register
static async register(data) {
try {
// if (!validateUser(username, password)) {
// throw new ErrorHandler(403, "Invalid login");
// }
const existingEmail = await getUserByUserEmailDb(data.user_email);
const existingUsername = await getUserByUsernameDb(data.user_name);
const user = await getUserByUsernameDb(username, tenantId);
console.log(user);
if (!user) {
throw new ErrorHandler(403, "Username not found.");
if (existingUsername) {
throw new ErrorHandler(400, 'Username is already taken');
}
if (existingEmail) {
throw new ErrorHandler(400, 'Email is already taken');
}
const isCorrectPassword = password === user.password
if (!isCorrectPassword) {
throw new ErrorHandler(403, "Username or password incorrect.");
}
const hashedPassword = await hashPassword(data.user_password);
const dataToken = {
tenant_id: tenantId,
user_id: user.user_id,
username,
fullname: user.full_name,
role_id: user.role_id
}
const userId = await createUserDb({
user_fullname: data.user_fullname,
user_name: data.user_name,
user_email: data.user_email,
user_phone: data.user_phone,
user_password: hashedPassword,
is_sa: 0,
is_active: 1,
is_approve: 1,
});
const token = await this.signToken(dataToken);
const refreshToken = await this.signRefreshToken(dataToken);
return {
token,
refreshToken,
role_id: dataToken.role_id,
tenant_id: tenantId,
user: {
user_id: dataToken.user_id,
fullname: dataToken.fullname,
username: dataToken.username,
},
const newUser = {
user_id: userId,
user_fullname: data.user_fullname,
user_name: data.user_name,
user_email: data.user_email,
user_phone: data.user_phone
};
return { user: newUser };
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
async signToken(data) {
// Login
static async login(data) {
try {
// console.log("signToken process.env.SECRET", process.env.SECRET)
return jwt.sign(data, process.env.SECRET, { expiresIn: "23h" });
const { identifier, password, captcha, captchaText } = data;
if (!captcha || captcha.toLowerCase() !== captchaText.toLowerCase()) {
throw new ErrorHandler(400, 'Invalid captcha');
}
let user;
if (identifier.includes('@')) {
user = await getUserByUserEmailDb(identifier);
} else {
user = await getUserByUsernameDb(identifier);
}
if (!user) throw new ErrorHandler(401, 'Invalid credentials');
const passwordMatch = await comparePassword(password, user.user_password);
if (!passwordMatch) throw new ErrorHandler(401, 'Invalid credentials');
if (!user.is_active) 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,
user_name: user.user_name,
user_email: user.user_email,
user_phone: user.user_phone,
role_id: user.role_id,
role_name: user.role_name,
is_sa: user.is_sa
};
const tokens = JWTService.generateTokenPair(payload);
return { user: payload, tokens };
} catch (error) {
logger.error(error);
throw new ErrorHandler(500, "An error occurred");
throw new ErrorHandler(error.statusCode, error.message);
}
}
async signRefreshToken(data) {
// Refresh Token
static async refreshToken(refreshToken) {
try {
return jwt.sign(data, process.env.REFRESH_SECRET, { expiresIn: "23h" });
let decoded;
try {
decoded = JWTService.verifyRefreshToken(refreshToken);
} catch (err) {
if (err.message.includes('expired')) {
throw new ErrorHandler(401, 'Refresh token expired');
}
throw new ErrorHandler(401, 'Invalid refresh token');
}
const payload = {
user_id: decoded.user_id,
user_fullname: decoded.user_fullname,
user_name: decoded.user_name,
user_email: decoded.user_email,
user_phone: decoded.user_phone,
role_id: decoded.role_id,
role_name: decoded.role_name,
is_sa: decoded.is_sa
};
const accessToken = JWTService.generateAccessToken(payload);
return {
accessToken,
tokenType: 'Bearer',
expiresIn: 900
};
} catch (error) {
logger.error(error);
throw new ErrorHandler(500, error.message);
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = new AuthService();
module.exports = AuthService;

View File

@@ -0,0 +1,88 @@
const {
getAllDevicesDb,
getDeviceByIdDb,
createDeviceDb,
updateDeviceDb,
deleteDeviceDb
} = require('../db/device.db');
const { ErrorHandler } = require('../helpers/error');
class DeviceService {
// Get all devices
static async getAllDevices(param) {
try {
const results = await getAllDevicesDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get device by ID
static async getDeviceById(id) {
try {
const result = await getDeviceByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Device not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create device
static async createDevice(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createDeviceDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update device
static async updateDevice(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getDeviceByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Device not found');
}
const result = await updateDeviceDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete device
static async deleteDevice(id, userId) {
try {
const dataExist = await getDeviceByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Device not found');
}
const result = await deleteDeviceDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = DeviceService;

88
services/roles.service.js Normal file
View File

@@ -0,0 +1,88 @@
const {
getAllRolesDb,
getRolesByIdDb,
insertRolesDb,
updateRolesDb,
deleteRolesDb
} = require('../db/roles.db');
const { ErrorHandler } = require('../helpers/error');
class RolesService {
// Get all Roles
static async getAllRoles(param) {
try {
const results = await getAllRolesDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Roles by ID
static async getRolesById(id) {
try {
const result = await getRolesByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Roles not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Roles
static async createRoles(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertRolesDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Roles
static async updateRoles(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getRolesByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Roles not found');
}
const result = await updateRolesDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Roles
static async deleteRoles(id, userId) {
try {
const dataExist = await getRolesByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Roles not found');
}
const result = await deleteRolesDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = RolesService;

View File

@@ -0,0 +1,88 @@
const {
getAllScheduleDb,
getScheduleByIdDb,
insertScheduleDb,
updateScheduleDb,
deleteScheduleDb
} = require('../db/schedule.db');
const { ErrorHandler } = require('../helpers/error');
class ScheduleService {
// Get all Schedule
static async getAllSchedule(param) {
try {
const results = await getAllScheduleDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Schedule by ID
static async getScheduleById(id) {
try {
const result = await getScheduleByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Schedule not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Schedule
static async insertScheduleDb(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertScheduleDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Schedule
static async updateSchedule(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Schedule not found');
}
const result = await updateScheduleDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Schedule
static async deleteSchedule(id, userId) {
try {
const dataExist = await getScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Schedule not found');
}
const result = await deleteScheduleDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ScheduleService;

88
services/shift.service.js Normal file
View File

@@ -0,0 +1,88 @@
const {
getAllShiftDb,
getShiftByIdDb,
insertShiftDb,
updateShiftDb,
deleteShiftDb
} = require('../db/shift.db');
const { ErrorHandler } = require('../helpers/error');
class ShiftService {
// Get all Shift
static async getAllShift(param) {
try {
const results = await getAllShiftDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get Shift by ID
static async getShiftById(id) {
try {
const result = await getShiftByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Shift not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create Shift
static async createShift(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertShiftDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update Shift
static async updateShift(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getShiftByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Shift not found');
}
const result = await updateShiftDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete Shift
static async deleteShift(id, userId) {
try {
const dataExist = await getShiftByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Shift not found');
}
const result = await deleteShiftDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = ShiftService;

View File

@@ -0,0 +1,92 @@
const {
getAllStatusDb,
getStatusByIdDb,
createStatusDb,
updateStatusDb,
deleteStatusDb,
checkStatusNumberExistsDb
} = require('../db/status.db');
const { ErrorHandler } = require('../helpers/error');
class StatusService {
// Get all status
static async getAllStatus(param) {
try {
const results = await getAllStatusDb(param);
results.data.map(element => {
});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get status by ID
static async getStatusById(id) {
try {
const result = await getStatusByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Status not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
static async createStatus(data) {
try {
if (!data || typeof data !== 'object') data = {};
if (data.status_number) {
const exists = await checkStatusNumberExistsDb(data.status_number);
if (exists) throw new ErrorHandler(400, 'Status number already exists');
}
const result = await createStatusDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
// Update status
static async updateStatus(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getStatusByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Status not found');
}
const result = await updateStatusDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete status
static async deleteStatus(id, userId) {
try {
const dataExist = await getStatusByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Status not found');
}
const result = await deleteStatusDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = StatusService;

View File

@@ -0,0 +1,87 @@
const {
getAllSubSectionsDb,
getSubSectionByIdDb,
createSubSectionDb,
updateSubSectionDb,
deleteSubSectionDb
} = require('../db/sub_section.db');
const { ErrorHandler } = require('../helpers/error');
class SubSectionService {
// Get all sub sections
static async getAll(param) {
try {
const results = await getAllSubSectionsDb(param);
results.data.map(el => {});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get sub section by ID
static async getById(id) {
try {
const result = await getSubSectionByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Sub section not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create sub section
static async create(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createSubSectionDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update sub section
static async update(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getSubSectionByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Sub section not found');
}
const result = await updateSubSectionDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete sub section
static async delete(id, userId) {
try {
const dataExist = await getSubSectionByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Sub section not found');
}
const result = await deleteSubSectionDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = SubSectionService;

89
services/tags.service.js Normal file
View File

@@ -0,0 +1,89 @@
const {
getAllTagsDb,
getTagsByIdDb,
createTagsDb,
updateTagsDb,
deleteTagsDb
} = require('../db/tags.db');
const { ErrorHandler } = require('../helpers/error');
class TagsService {
// Get all devices
static async getAllTags(param) {
try {
const results = await getAllTagsDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get device by ID
static async getTagByID(id) {
try {
const result = await getTagsByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Tags not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create device
static async createTags(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createTagsDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update device
static async updateTags(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getTagsByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Tags not found');
}
const result = await updateTagsDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete device
static async deleteTags(id, userId) {
try {
const dataExist = await getTagsByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Tags not found');
}
const result = await deleteTagsDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = TagsService;

88
services/unit.service.js Normal file
View File

@@ -0,0 +1,88 @@
const {
getAllUnitsDb,
getUnitByIdDb,
createUnitDb,
updateUnitDb,
deleteUnitDb
} = require('../db/unit.db');
const { ErrorHandler } = require('../helpers/error');
class UnitService {
// Get all units
static async getAllUnits(param) {
try {
const results = await getAllUnitsDb(param);
results.data.map(element => {
});
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get unit by ID
static async getUnitById(id) {
try {
const result = await getUnitByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'Unit not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create unit
static async createUnit(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await createUnitDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update unit
static async updateUnit(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getUnitByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Unit not found');
}
const result = await updateUnitDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete unit
static async deleteUnit(id, userId) {
try {
const dataExist = await getUnitByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'Unit not found');
}
const result = await deleteUnitDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = UnitService;

View File

@@ -1,124 +1,188 @@
const {
createUserDb,
changeUserPasswordDb,
getUserByIdDb,
updateUserDb,
deleteUserDb,
getAllUsersDb,
getUserByIdDb,
getUserByUserEmailDb,
getUserByUsernameDb,
getAllRoleDb
} = require("../db/user.db");
const { ErrorHandler } = require("../helpers/error");
const { convertId } = require("../helpers/utils");
const statusName = [
{
status: true,
status_name: "Aktif"
}, {
status: false,
status_name: "NonAktif"
}
];
createUserDb,
updateUserDb,
approveUserDb,
rejectUserDb,
deleteUserDb,
changeUserPasswordDb
} = require('../db/user.db');
const { hashPassword } = require('../helpers/hashPassword');
const { ErrorHandler } = require('../helpers/error');
class UserService {
getAllStatusUsers = async () => {
try {
return statusName;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
getAllUsers = async (param) => {
// Get all users
static async getAllUsers(param) {
try {
const results = await getAllUsersDb(param);
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')
});
return results
return results;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
}
createUser = async (param) => {
// Get user by ID
static async getUserById(id) {
try {
const userByUsername = await getUserByUsernameDb(param.userName, param.tenantID);
const result = await getUserByIdDb(id);
if (userByUsername) {
throw new ErrorHandler(401, "username taken already");
if (!result) throw new ErrorHandler(404, 'User not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create user
static async createUser(data) {
try {
if (!data || typeof data !== 'object') data = {};
const creatorId = data.userId || null;
// cek duplikasi username & email
const [existingUsername, existingEmail] = await Promise.all([
getUserByUsernameDb(data.user_name),
getUserByUserEmailDb(data.user_email)
]);
if (existingUsername) throw new ErrorHandler(400, 'Username is already taken');
if (existingEmail) throw new ErrorHandler(400, 'Email is already taken');
// hash password
const hashedPassword = await hashPassword(data.user_password);
// siapkan data untuk insert
const userData = {
...data,
user_password: hashedPassword,
is_approve: 2,
approved_by: creatorId,
created_by: creatorId,
updated_by: creatorId,
is_sa: 0,
is_active: 1
};
delete userData.userId;
const result = await createUserDb(userData);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
// Update user
static async updateUser(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const existingEmail = await getUserByUserEmailDb(data.user_email);
const existingUsername = await getUserByUsernameDb(data.user_name);
if (existingUsername) {
throw new ErrorHandler(400, 'Username is already taken');
}
if (existingEmail) {
throw new ErrorHandler(400, 'Email is already taken')
}
return await createUserDb(param);
const userExist = await getUserByIdDb(id);
if (!userExist) throw new ErrorHandler(404, 'User not found');
const result = await updateUserDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
}
getUserById = async (id) => {
// Approve user
static async approveUser(userId, approverId) {
try {
const user = await getUserByIdDb(id);
// user.password = undefined;
user.is_active = user.is_active == 1 ? true : false
return user;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
changeUserPassword = async (password, email, tenantID) => {
try {
return await changeUserPasswordDb(password, email, tenantID);
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
updateUser = async (param) => {
const { userName, id } = param;
const errors = {};
try {
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";
if (!userId) {
throw new ErrorHandler(400, 'User ID is required');
}
if (Object.keys(errors).length > 0) {
throw new ErrorHandler(403, errors);
const existingUser = await getUserByIdDb(userId);
if (!existingUser) {
throw new ErrorHandler(404, 'User not found');
}
return await updateUserDb(param);
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
if (existingUser.is_approve === 2) {
throw new ErrorHandler(400, 'User is already approved');
}
deleteUser = async (id, userID) => {
try {
return await deleteUserDb(id, userID);
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
if (existingUser.is_approve === 0) {
throw new ErrorHandler(400, 'User is already rejected');
}
getAllRoles = async (tenantID) => {
const updatedUser = await approveUserDb(userId, approverId);
return updatedUser;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
// Reject user
static async rejectUser(userId, approverId) {
try {
return await getAllRoleDb(tenantID);
if (!userId) {
throw new ErrorHandler(400, 'User ID is required');
}
const existingUser = await getUserByIdDb(userId);
if (!existingUser) {
throw new ErrorHandler(404, 'User not found');
}
if (existingUser.is_approve === 2) {
throw new ErrorHandler(400, 'User is already approved');
}
if (existingUser.is_approve === 0) {
throw new ErrorHandler(400, 'User is already rejected');
}
const updatedUser = await rejectUserDb(userId, approverId);
return updatedUser;
} catch (error) {
throw new ErrorHandler(error.statusCode || 500, error.message);
}
}
// Soft delete user
static async deleteUser(id, userId) {
try {
const userExist = await getUserByIdDb(id);
if (!userExist) throw new ErrorHandler(404, 'User not found');
const result = await deleteUserDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
};
}
// Change password
static async changeUserPassword(id, newPassword) {
try {
const userExist = await getUserByIdDb(id);
if (!userExist) throw new ErrorHandler(404, 'User not found');
const hashedPassword = await hashPassword(newPassword);
const result = await changeUserPasswordDb(id, hashedPassword);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = new UserService();
module.exports = UserService;

8
utils/captcha.js Normal file
View File

@@ -0,0 +1,8 @@
const svgCaptcha = require('svg-captcha');
function createCaptcha() {
const captcha = svgCaptcha.create({ size: 5, noise: 7, color: true });
return { svg: captcha.data, text: captcha.text };
}
module.exports = { createCaptcha };

8
utils/date.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
formattedDate: (timestamp) => {
let date = new Date(timestamp);
let options = { day: "numeric", month: "long", year: "numeric" };
let formattedDate = date.toISOString("id-ID", options);
return formattedDate;
},
};

73
utils/jwt.js Normal file
View File

@@ -0,0 +1,73 @@
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const tokenSettings = {
access: {
expiresIn: '15m',
type: 'access',
secret: process.env.SECRET
},
refresh: {
expiresIn: '7d',
type: 'refresh',
secret: process.env.REFRESH_SECRET
}
};
function generateTokenId() {
return crypto.randomBytes(32).toString('hex');
}
function generateToken(payload, type) {
const settings = tokenSettings[type];
if (!settings) throw new Error(`Invalid token type: ${type}`);
const tokenPayload = { ...payload, type: settings.type };
return jwt.sign(tokenPayload, settings.secret, {
expiresIn: settings.expiresIn,
jwtid: generateTokenId()
});
}
function verifyTokenType(token, type) {
const settings = tokenSettings[type];
const decoded = jwt.verify(token, settings.secret);
if (decoded.type !== type) throw new Error('Invalid token type');
return decoded;
}
function generateAccessToken(payload) {
return generateToken(payload, 'access');
}
function generateRefreshToken(payload) {
return generateToken(payload, 'refresh');
}
function verifyToken(token) {
return verifyTokenType(token, 'access');
}
function verifyRefreshToken(token) {
return verifyTokenType(token, 'refresh');
}
function generateTokenPair(payload) {
const accessToken = generateAccessToken(payload);
const refreshToken = generateRefreshToken(payload);
return {
accessToken,
refreshToken,
tokenType: 'Bearer',
};
}
module.exports = {
generateAccessToken,
generateRefreshToken,
verifyToken,
verifyRefreshToken,
generateTokenPair,
};

40
validate/auth.schema.js Normal file
View File

@@ -0,0 +1,40 @@
const Joi = require("joi");
// ========================
// Auth Validation
// ========================
const registerSchema = Joi.object({
user_fullname: Joi.string().min(3).max(100).required(),
user_name: Joi.string().alphanum().min(3).max(50).required(),
user_email: Joi.string().email().required(),
user_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'
}),
user_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}'
})
});
const loginSchema = Joi.object({
identifier: Joi.string().required(),
password: Joi.string().required(),
captcha: Joi.string().required(),
captchaText: Joi.string().required()
});
module.exports = {
registerSchema,
loginSchema,
};

36
validate/device.schema.js Normal file
View File

@@ -0,0 +1,36 @@
// ========================
// Device Validation
const Joi = require("joi");
// ========================
const insertDeviceSchema = Joi.object({
device_name: Joi.string().max(100).required(),
is_active: Joi.boolean().required(),
device_location: Joi.string().max(100).required(),
device_description: Joi.string().required(),
ip_address: Joi.string()
.ip({ version: ['ipv4', 'ipv6'] })
.required()
.messages({
'string.ip': 'IP address must be a valid IPv4 or IPv6 address'
})
});
const updateDeviceSchema = Joi.object({
device_name: Joi.string().max(100),
is_active: Joi.boolean(),
device_location: Joi.string().max(100),
device_description: Joi.string(),
ip_address: Joi.string()
.ip({ version: ['ipv4', 'ipv6'] })
.messages({
'string.ip': 'IP address must be a valid IPv4 or IPv6 address'
})
}).min(1);
// ✅ Export dengan CommonJS
module.exports = {
insertDeviceSchema, updateDeviceSchema
};

25
validate/roles.schema.js Normal file
View File

@@ -0,0 +1,25 @@
// ========================
// Device Validation
const Joi = require("joi");
// ========================
const insertRolesSchema = Joi.object({
role_name: Joi.string().max(100).required(),
role_level: Joi.number().required(),
role_description: Joi.string().max(100).required(),
is_active: Joi.boolean().required(),
});
const updateRolesSchema = Joi.object({
role_name: Joi.string().max(100).optional(),
role_level: Joi.number().optional(),
role_description: Joi.string().max(100).optional(),
is_active: Joi.boolean().optional()
}).min(1);
// ✅ Export dengan CommonJS
module.exports = {
insertRolesSchema, updateRolesSchema
};

View File

@@ -0,0 +1,29 @@
// ========================
// Schedule Validation
const Joi = require("joi");
const datePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
const insertScheduleSchema = Joi.object({
schedule_date: Joi.string().pattern(datePattern).required().messages({
"string.pattern.base": "schedule_date harus dalam format YYYY-MM-DD",
"any.required": "schedule_date wajib diisi",
}),
is_active: Joi.boolean().required(),
shift_id: Joi.number(),
next_day: Joi.number().required(),
});
const updateScheduleSchema = Joi.object({
schedule_date: Joi.string().pattern(datePattern).messages({
"string.pattern.base": "schedule_date harus dalam format YYYY-MM-DD",
}),
is_active: Joi.boolean(),
shift_id: Joi.number(),
}).min(1);
module.exports = {
insertScheduleSchema,
updateScheduleSchema,
};

44
validate/shift.schema.js Normal file
View File

@@ -0,0 +1,44 @@
// ========================
// Device Validation
const Joi = require("joi");
// ========================
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/;
const insertShiftSchema = Joi.object({
shift_name: Joi.string().max(100).required(),
is_active:Joi.boolean().required(),
start_time: Joi.string()
.pattern(timePattern)
.required()
.messages({
"string.pattern.base": "start_time harus dalam format HH:mm atau HH:mm:ss",
}),
end_time: Joi.string()
.pattern(timePattern)
.required()
.messages({
"string.pattern.base": "end_time harus dalam format HH:mm atau HH:mm:ss",
}),
});
const updateShiftSchema = Joi.object({
shift_name: Joi.string().max(100).optional(),
is_active:Joi.boolean().optional(),
start_time: Joi.string()
.pattern(timePattern)
.messages({
"string.pattern.base": "start_time harus dalam format HH:mm atau HH:mm:ss",
}).optional(),
end_time: Joi.string()
.pattern(timePattern)
.messages({
"string.pattern.base": "end_time harus dalam format HH:mm atau HH:mm:ss",
}),
}).min(1);
module.exports = {
insertShiftSchema,
updateShiftSchema,
};

26
validate/status.schema.js Normal file
View File

@@ -0,0 +1,26 @@
const Joi = require("joi");
// ========================
// Status Validation
// ========================
const insertStatusSchema = Joi.object({
status_number: Joi.number().integer().required(),
status_name: Joi.string().max(200).required(),
status_color: Joi.string().max(200).required(),
status_description: Joi.string().allow('', null),
is_active: Joi.boolean().required(),
});
const updateStatusSchema = Joi.object({
status_number: Joi.number().integer().optional(),
status_name: Joi.string().max(200).optional(),
status_color: Joi.string().max(200).optional(),
status_description: Joi.string().allow('', null).optional(),
is_active: Joi.boolean().optional()
}).min(1);
// ✅ Export dengan CommonJS
module.exports = {
insertStatusSchema,
updateStatusSchema
};

View File

@@ -0,0 +1,33 @@
const Joi = require("joi");
// ========================
// Plant Sub Section Validation
// ========================
const insertSubSectionSchema = Joi.object({
sub_section_name: Joi.string()
.max(200)
.required()
.messages({
"string.base": "Sub section name must be a string",
"string.max": "Sub section name cannot exceed 200 characters",
"any.required": "Sub section name is required"
}).required(),
is_active: Joi.boolean().required(),
});
const updateSubSectionSchema = Joi.object({
sub_section_name: Joi.string()
.max(200)
.messages({
"string.base": "Sub section name must be a string",
"string.max": "Sub section name cannot exceed 200 characters",
}).optional(),
is_active: Joi.boolean().optional(),
}).min(1).messages({
"object.min": "At least one field must be provided to update",
});
module.exports = {
insertSubSectionSchema,
updateSubSectionSchema
};

33
validate/tags.schema.js Normal file
View File

@@ -0,0 +1,33 @@
// ========================
// Device Validation
const Joi = require("joi");
// ========================
const insertTagsSchema = Joi.object({
device_id: Joi.number().optional(),
tag_name: Joi.string().max(200).required(),
tag_number: Joi.number().required(),
is_active: Joi.boolean().required(),
data_type: Joi.string().max(50).required(),
unit: Joi.string().max(50).required(),
sub_section_id: Joi.number().optional(),
is_alarm: Joi.boolean().required()
});
const updateTagsSchema = Joi.object({
device_id: Joi.number(),
tag_name: Joi.string().max(200),
tag_number: Joi.number(),
is_active: Joi.boolean(),
data_type: Joi.string().max(50),
unit: Joi.string().max(50),
is_alarm: Joi.boolean().optional(),
sub_section_id: Joi.number().optional(),
}).min(1);
// ✅ Export dengan CommonJS
module.exports = {
insertTagsSchema,
updateTagsSchema,
};

21
validate/unit.schema.js Normal file
View File

@@ -0,0 +1,21 @@
const Joi = require("joi");
// ========================
// Unit Validation
// ========================
const insertUnitSchema = Joi.object({
unit_name: Joi.string().max(100).required(),
tag_id: Joi.number().integer().optional(),
is_active: Joi.boolean().required(),
});
const updateUnitSchema = Joi.object({
unit_name: Joi.string().max(100).optional(),
tag_id: Joi.number().integer().optional(),
is_active: Joi.boolean().optional()
}).min(1);
module.exports = {
insertUnitSchema,
updateUnitSchema
};

61
validate/user.schema.js Normal file
View File

@@ -0,0 +1,61 @@
const Joi = require("joi");
// ========================
// Users Validation
// ========================
const userSchema = Joi.object({
user_fullname: Joi.string().min(3).max(100).required(),
user_name: Joi.string().alphanum().min(3).max(50).required(),
user_email: Joi.string().email().required(),
user_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'
}),
user_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)
});
const updateUserSchema = Joi.object({
user_fullname: Joi.string().min(3).max(100).optional(),
user_name: Joi.string().alphanum().min(3).max(50).optional(),
user_email: Joi.string().email().optional(),
user_phone: Joi.string()
.pattern(/^(?:\+62|0)8\d{7,10}$/)
.message('Phone number must be a valid Indonesian number in format +628XXXXXXXXX')
.optional(),
role_id: Joi.number().integer().min(1).optional(),
is_active: Joi.boolean().optional()
}).min(1);
const newPasswordSchema = Joi.object({
new_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}'
})
});
module.exports = {
userSchema,
newPasswordSchema,
updateUserSchema
};