“init”
This commit is contained in:
43
.env.example
Normal file
43
.env.example
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# # SQL DB Connection Colo
|
||||||
|
# SQL_HOST=117.102.231.130
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Application Port - express server listens on this port (default 9000).
|
||||||
|
PORT=9528
|
||||||
|
ENDPOINT_WA=http://203.153.114.226:9529/send
|
||||||
|
# ENDPOINT_WA=http://localhost:9529/send
|
||||||
|
ENDPOINT_FE=http://203.153.114.226:9527
|
||||||
|
|
||||||
|
# JWT access secret
|
||||||
|
SECRET=secret
|
||||||
|
|
||||||
|
# JWT refresh secret
|
||||||
|
REFRESH_SECRET=refreshsecret
|
||||||
|
|
||||||
|
# mail server settings
|
||||||
|
# SMTP_FROM=youremail
|
||||||
|
# SMTP_USER=youremail
|
||||||
|
|
||||||
|
# Stripe secret key - https://stripe.com/docs/keys
|
||||||
|
# STRIPE_SECRET_KEY=sk_test_4eC39HqLyjWDarjtT1zdp7dc
|
||||||
|
|
||||||
|
# Google OAuth2.0 settings for sign in with Google - https://console.developers.google.com/
|
||||||
|
# OAUTH_CLIENT_ID=287280guajkxxxxxxx.apps.googleusercontent.com
|
||||||
|
# OAUTH_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
|
||||||
|
# OAUTH_REFRESH_TOKEN=1//XXXXXXXXXX
|
||||||
|
|
||||||
|
# Google OAuth2.0 settings for sending emails - https://console.developers.google.com/
|
||||||
|
# CLIENT_ID=938729280guajk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
|
||||||
|
# CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
# REFRESH_TOKEN=1//XXXXXXXX
|
||||||
|
|
||||||
|
VITE_KEY_SESSION=PetekRombonganPetekMorekMorakMarek
|
||||||
20
.eslintrc.js
Normal file
20
.eslintrc.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
commonjs: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
extends: ["eslint:recommended"],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12,
|
||||||
|
},
|
||||||
|
parser: "babel-eslint",
|
||||||
|
plugins: ["babel", "prettier"],
|
||||||
|
rules: {
|
||||||
|
"no-console": "warn",
|
||||||
|
eqeqeq: "error",
|
||||||
|
// "object-curly-spacing": ["error", "always"],
|
||||||
|
// "arrow-spacing": ["error", { before: true, after: true }],
|
||||||
|
},
|
||||||
|
};
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
request.http
|
||||||
|
*.rest
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
30
app.js
Normal file
30
app.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const express = require("express");
|
||||||
|
require("express-async-errors");
|
||||||
|
const cors = require("cors");
|
||||||
|
const morgan = require("morgan");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
const routes = require("./routes");
|
||||||
|
const helmet = require("helmet");
|
||||||
|
const compression = require("compression");
|
||||||
|
const unknownEndpoint = require("./middleware/unKnownEndpoint");
|
||||||
|
const { handleError } = require("./helpers/error");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.set("trust proxy", 1);
|
||||||
|
app.use(cors({ credentials: true, origin: true }));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(morgan("dev"));
|
||||||
|
app.use(compression());
|
||||||
|
app.use(helmet());
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
app.use("/api", routes);
|
||||||
|
|
||||||
|
app.get("/", (req, res) =>
|
||||||
|
res.send("<h1 style='text-align: center'>HAHALO</h1>")
|
||||||
|
);
|
||||||
|
app.use(unknownEndpoint);
|
||||||
|
app.use(handleError);
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
204
config/index.js
Normal file
204
config/index.js
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const sql = require("mssql");
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
// Config SQL Server
|
||||||
|
const config = {
|
||||||
|
user: process.env.SQL_USERNAME,
|
||||||
|
password: process.env.SQL_PASSWORD,
|
||||||
|
database: process.env.SQL_DATABASE,
|
||||||
|
server: process.env.SQL_HOST,
|
||||||
|
port: parseInt(process.env.SQL_PORT, 10),
|
||||||
|
options: {
|
||||||
|
encrypt: false, // true kalau Azure
|
||||||
|
trustServerCertificate: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Buat pool global
|
||||||
|
const poolPromise = new sql.ConnectionPool(config)
|
||||||
|
.connect()
|
||||||
|
.then(pool => {
|
||||||
|
console.log("✅ Koneksi SQL Server berhasil");
|
||||||
|
return pool;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("❌ Gagal koneksi SQL Server:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper query (auto konversi $1 → @p1)
|
||||||
|
*/
|
||||||
|
async function query(text, params = []) {
|
||||||
|
const pool = await poolPromise;
|
||||||
|
const request = pool.request();
|
||||||
|
|
||||||
|
params.forEach((param, i) => {
|
||||||
|
request.input(`p${i + 1}`, param);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ubah $1, $2 jadi @p1, @p2
|
||||||
|
const sqlText = text.replace(/\$(\d+)/g, (_, num) => `@p${num}`);
|
||||||
|
|
||||||
|
console.log(sqlText, params);
|
||||||
|
return request.query(sqlText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build filter query
|
||||||
|
*/
|
||||||
|
function buildFilterQuery(filterQuery = [], fixedParams = []) {
|
||||||
|
let whereConditions = [];
|
||||||
|
let queryParams = [...fixedParams];
|
||||||
|
|
||||||
|
filterQuery.forEach((f) => {
|
||||||
|
if (f.param === undefined || f.param === null || f.param === "") return;
|
||||||
|
|
||||||
|
switch (f.type) {
|
||||||
|
case "string":
|
||||||
|
queryParams.push(`%${f.param}%`);
|
||||||
|
whereConditions.push(`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "number":
|
||||||
|
queryParams.push(f.param);
|
||||||
|
whereConditions.push(`${f.column} = $${queryParams.length}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "boolean":
|
||||||
|
queryParams.push(f.param ? 1 : 0);
|
||||||
|
whereConditions.push(`${f.column} = $${queryParams.length}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { whereConditions, queryParams };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build OR ILIKE (SQL Server pakai LIKE + COLLATE)
|
||||||
|
*/
|
||||||
|
function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
|
||||||
|
if (!criteria) return { whereClause: "", whereParam: fixedParams };
|
||||||
|
|
||||||
|
let orStringConditions = [];
|
||||||
|
let queryParams = [...fixedParams];
|
||||||
|
|
||||||
|
columnParam.forEach((column) => {
|
||||||
|
if (!column) return;
|
||||||
|
queryParams.push(`%${criteria}%`);
|
||||||
|
orStringConditions.push(`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const whereClause = orStringConditions.length
|
||||||
|
? `AND (${orStringConditions.join(" OR ")})`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return { whereOrConditions: whereClause, whereParam: queryParams };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build dynamic UPDATE
|
||||||
|
*/
|
||||||
|
function buildDynamicUpdate(table, data, where) {
|
||||||
|
const setParts = [];
|
||||||
|
const values = [];
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
setParts.push(`${key} = $${index++}`);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setParts.length === 0) {
|
||||||
|
throw new Error("Tidak ada kolom untuk diupdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// updated_at otomatis pakai GETDATE()
|
||||||
|
setParts.push(`updated_at = GETDATE()`);
|
||||||
|
|
||||||
|
const whereParts = [];
|
||||||
|
for (const [key, value] of Object.entries(where)) {
|
||||||
|
whereParts.push(`${key} = $${index++}`);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
UPDATE ${table}
|
||||||
|
SET ${setParts.join(", ")}
|
||||||
|
WHERE ${whereParts.join(" AND ")}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return { query, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build dynamic INSERT
|
||||||
|
*/
|
||||||
|
function buildDynamicInsert(table, data) {
|
||||||
|
const columns = [];
|
||||||
|
const placeholders = [];
|
||||||
|
const values = [];
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
columns.push(key);
|
||||||
|
placeholders.push(`$${index++}`);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.length === 0) {
|
||||||
|
throw new Error("Tidak ada kolom untuk diinsert");
|
||||||
|
}
|
||||||
|
|
||||||
|
// created_at & updated_at otomatis
|
||||||
|
columns.push("created_at", "updated_at");
|
||||||
|
placeholders.push("GETDATE()", "GETDATE()");
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
INSERT INTO ${table} (${columns.join(", ")})
|
||||||
|
VALUES (${placeholders.join(", ")});
|
||||||
|
SELECT SCOPE_IDENTITY() as inserted_id;
|
||||||
|
`;
|
||||||
|
|
||||||
|
return { query, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate kode otomatis
|
||||||
|
*/
|
||||||
|
async function generateKode(prefix, tableName, columnName) {
|
||||||
|
const pool = await poolPromise;
|
||||||
|
const result = await pool.request()
|
||||||
|
.input("prefix", sql.VarChar, prefix + "%")
|
||||||
|
.query(`
|
||||||
|
SELECT TOP 1 ${columnName} as kode
|
||||||
|
FROM ${tableName}
|
||||||
|
WHERE ${columnName} LIKE @prefix
|
||||||
|
ORDER BY ${columnName} DESC
|
||||||
|
`);
|
||||||
|
|
||||||
|
let nextNumber = 1;
|
||||||
|
if (result.recordset.length > 0) {
|
||||||
|
const lastKode = result.recordset[0].kode;
|
||||||
|
const lastNumber = parseInt(lastKode.replace(prefix, ""), 10);
|
||||||
|
nextNumber = lastNumber + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + String(nextNumber).padStart(3, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
query,
|
||||||
|
buildFilterQuery,
|
||||||
|
buildStringOrIlike,
|
||||||
|
buildDynamicInsert,
|
||||||
|
buildDynamicUpdate,
|
||||||
|
generateKode,
|
||||||
|
};
|
||||||
26
controllers/auth.controller.js
Normal file
26
controllers/auth.controller.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const authService = require("../services/auth.service");
|
||||||
|
|
||||||
|
const loginUser = async (req, res) => {
|
||||||
|
const { username, password, role, tenant } = req.body;
|
||||||
|
const { token, refreshToken, user } = await authService.login(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
tenant
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loginUser,
|
||||||
|
};
|
||||||
172
controllers/users.controller.js
Normal file
172
controllers/users.controller.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
// Definisikan skema validasi
|
||||||
|
const validateTerm = Joi.object({
|
||||||
|
user_fullname: Joi.string().max(255).required(),
|
||||||
|
user_name: Joi.string().max(255).required(),
|
||||||
|
user_email: Joi.string().max(255).email().allow(null),
|
||||||
|
user_password: Joi.string().max(255).required(),
|
||||||
|
role_id: Joi.number().integer().allow(null),
|
||||||
|
is_active: Joi.boolean().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAllUsers = async (req, res) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 10,
|
||||||
|
fullname: userFullname,
|
||||||
|
username: userName,
|
||||||
|
is_active: isActive,
|
||||||
|
criteria,
|
||||||
|
tenantID,
|
||||||
|
} = req.query
|
||||||
|
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
const filterQuery = {
|
||||||
|
fixed: {
|
||||||
|
limit, offset, tenantID
|
||||||
|
},
|
||||||
|
filterQuery: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
column: 'user_fullname',
|
||||||
|
param: userFullname
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
column: 'user_name',
|
||||||
|
param: userName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
column: 'is_active',
|
||||||
|
param: isActive
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filterCriteria:
|
||||||
|
{
|
||||||
|
criteria,
|
||||||
|
column: [
|
||||||
|
'user_fullname', 'user_name'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await userService.getAllUsers(filterQuery)
|
||||||
|
const response = await setResponsePaging(results.data, results.total, parseInt(limit), parseInt(page))
|
||||||
|
|
||||||
|
res.status(response.statusCode).json(response)
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllStatusUsers = async (req, res) => {
|
||||||
|
|
||||||
|
const results = await userService.getAllStatusUsers();
|
||||||
|
const response = await setResponse(results)
|
||||||
|
|
||||||
|
res.status(response.statusCode).json(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async (req, res) => {
|
||||||
|
|
||||||
|
// Lakukan validasi
|
||||||
|
const { error } = validateTerm.validate(req.body, { stripUnknown: true });
|
||||||
|
if (error) {
|
||||||
|
const response = await setResponse([], error.details[0].message, 400)
|
||||||
|
return res.status(response.statusCode).json(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await setResponse(results);
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await setResponse(results);
|
||||||
|
|
||||||
|
res.status(response.statusCode).json(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUser = async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const userID = req.userID
|
||||||
|
|
||||||
|
const results = await userService.deleteUser(id, userID);
|
||||||
|
const response = await setResponse(results)
|
||||||
|
|
||||||
|
res.status(response.statusCode).json(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllRoles = async (req, res) => {
|
||||||
|
const results = await userService.getAllRoles(req.body.tenantID);
|
||||||
|
const response = await setResponse(results)
|
||||||
|
|
||||||
|
res.status(response.statusCode).json(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAllUsers,
|
||||||
|
createUser,
|
||||||
|
getUserById,
|
||||||
|
updateUser,
|
||||||
|
deleteUser,
|
||||||
|
getUserProfile,
|
||||||
|
getAllRoles,
|
||||||
|
getAllStatusUsers
|
||||||
|
};
|
||||||
142
db/user.db.js
Normal file
142
db/user.db.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
const pool = require("../config");
|
||||||
|
|
||||||
|
const getAllUsersDb = async (param) => {
|
||||||
|
// limit & offset masuk fixed param
|
||||||
|
let fixedParams = [param.fixed.limit, param.fixed.offset, param.fixed.tenantID];
|
||||||
|
|
||||||
|
const { whereOrConditions, whereParam } = pool.buildStringOrIlike(
|
||||||
|
param.filterCriteria.column,
|
||||||
|
param.filterCriteria.criteria,
|
||||||
|
fixedParams
|
||||||
|
);
|
||||||
|
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
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await pool.query(query, queryParams);
|
||||||
|
const rows = result.recordset;
|
||||||
|
|
||||||
|
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 getUserByIdDb = async (id) => {
|
||||||
|
const query = `
|
||||||
|
SELECT mut.*
|
||||||
|
FROM m_users mut
|
||||||
|
WHERE mut.user_id = $1
|
||||||
|
`;
|
||||||
|
const result = await pool.query(query, [id]);
|
||||||
|
return result.recordset[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserByUsernameDb = async (username) => {
|
||||||
|
const query = `
|
||||||
|
SELECT mut.*
|
||||||
|
FROM m_users mut
|
||||||
|
WHERE LOWER(mut.username) = LOWER($1)
|
||||||
|
`;
|
||||||
|
const result = await pool.query(query, [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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
`;
|
||||||
|
const result = await pool.query(query, [userID, id]);
|
||||||
|
return result.recordset[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeUserPasswordDb = async (hashedPassword, userEmail, tenantId) => {
|
||||||
|
const query = `
|
||||||
|
UPDATE m_users
|
||||||
|
SET user_password = $1
|
||||||
|
WHERE user_email = $2 AND tenant_id = $3
|
||||||
|
`;
|
||||||
|
return pool.query(query, [hashedPassword, userEmail, tenantId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllRoleDb = async (tenantId) => {
|
||||||
|
const query = `
|
||||||
|
SELECT *
|
||||||
|
FROM system.role_tenant
|
||||||
|
WHERE deleted_at IS NULL AND tenant_id = $1
|
||||||
|
`;
|
||||||
|
const result = await pool.query(query, [tenantId]);
|
||||||
|
return result.recordset;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAllUsersDb,
|
||||||
|
getUserByIdDb,
|
||||||
|
getUserByUserEmailDb,
|
||||||
|
updateUserDb,
|
||||||
|
createUserDb,
|
||||||
|
deleteUserDb,
|
||||||
|
getUserByUsernameDb,
|
||||||
|
changeUserPasswordDb,
|
||||||
|
getAllRoleDb,
|
||||||
|
};
|
||||||
14
ecosystem.config.js
Normal file
14
ecosystem.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "bengkel-api",
|
||||||
|
script: "./index.js", // Path to your entry file
|
||||||
|
env: {
|
||||||
|
NODE_ENV: "development",
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
NODE_ENV: "production",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
24
helpers/error.js
Normal file
24
helpers/error.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const { logger } = require("../utils/logger");
|
||||||
|
class ErrorHandler extends Error {
|
||||||
|
constructor(statusCode, message) {
|
||||||
|
super();
|
||||||
|
this.status = "error";
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleError = (err, req, res, next) => {
|
||||||
|
const { statusCode, message } = err;
|
||||||
|
logger.error(err);
|
||||||
|
res.status(statusCode || 500).json({
|
||||||
|
status: "error",
|
||||||
|
statusCode: statusCode || 500,
|
||||||
|
message: statusCode === 500 ? "An error occurred" : message,
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
module.exports = {
|
||||||
|
ErrorHandler,
|
||||||
|
handleError,
|
||||||
|
};
|
||||||
12
helpers/hashPassword.js
Normal file
12
helpers/hashPassword.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
|
||||||
|
const hashPassword = async (password) => {
|
||||||
|
const salt = await bcrypt.genSalt();
|
||||||
|
const hashedPassword = await bcrypt.hash(password, salt);
|
||||||
|
return hashedPassword;
|
||||||
|
};
|
||||||
|
|
||||||
|
const comparePassword = async (password, passwordHash) =>
|
||||||
|
await bcrypt.compare(password, passwordHash);
|
||||||
|
|
||||||
|
module.exports = { hashPassword, comparePassword };
|
||||||
13
helpers/test_helper.js
Normal file
13
helpers/test_helper.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const pool = require("../config");
|
||||||
|
|
||||||
|
const usersInDb = async () => {
|
||||||
|
const users = await pool.query("SELECT * FROM USERS");
|
||||||
|
return users.rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
const productsInDb = async () => {
|
||||||
|
const products = await pool.query("SELECT * FROM products");
|
||||||
|
return products.rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { usersInDb, productsInDb };
|
||||||
89
helpers/utils.js
Normal file
89
helpers/utils.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const setResponse = async (data = [], message = "success", statusCode = 200) => {
|
||||||
|
const response = {
|
||||||
|
data,
|
||||||
|
total: data.length,
|
||||||
|
message,
|
||||||
|
statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
};
|
||||||
|
|
||||||
|
const setResponsePaging = async (data = [], total, limit, page, message = "success", statusCode = 200) => {
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(total / limit);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
message,
|
||||||
|
statusCode,
|
||||||
|
data,
|
||||||
|
total: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertId(items, id, fieldId = "id", fieldName = "name") {
|
||||||
|
var match = ""
|
||||||
|
items.forEach(element => {
|
||||||
|
if (element[fieldId] == id) {
|
||||||
|
match = element[fieldName]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToYYYYMMDD(date) {
|
||||||
|
return new Intl.DateTimeFormat('id-ID', {
|
||||||
|
timeZone: 'Asia/Jakarta',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
}).format(date).split('/').reverse().join('-'); // Ubah format dari dd/mm/yyyy ke yyyy-mm-dd
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureArray(param) {
|
||||||
|
return Array.isArray(param) ? param : [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderByClauseQuery(orderParams) {
|
||||||
|
orderParams = ensureArray(orderParams)
|
||||||
|
|
||||||
|
// Transform order parameters to SQL ORDER BY syntax
|
||||||
|
const validDirections = ['ASC', 'DESC'];
|
||||||
|
const orderConditions = orderParams.map(param => {
|
||||||
|
const [field, direction] = param.split(':');
|
||||||
|
if (!validDirections.includes(direction.toUpperCase())) {
|
||||||
|
throw new Error(`Invalid direction: ${direction}`);
|
||||||
|
}
|
||||||
|
return `${field} ${direction}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gabungkan dengan koma untuk digunakan dalam query
|
||||||
|
const orderByClause = orderConditions.join(', ');
|
||||||
|
|
||||||
|
return orderByClause
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { setResponse, setResponsePaging, setPaging, convertId, formatToYYYYMMDD, orderByClauseQuery };
|
||||||
9
helpers/validateUser.js
Normal file
9
helpers/validateUser.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
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;
|
||||||
10
index.js
Normal file
10
index.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
require("dotenv").config({ path: __dirname + "/.env" });
|
||||||
|
const http = require("http");
|
||||||
|
const app = require("./app");
|
||||||
|
const { logger } = require("./utils/logger");
|
||||||
|
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 9524;
|
||||||
|
|
||||||
|
server.listen(PORT, () => logger.info(`Magic happening on port: ${PORT}`));
|
||||||
8
middleware/unKnownEndpoint.js
Normal file
8
middleware/unKnownEndpoint.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const { ErrorHandler } = require("../helpers/error");
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const unknownEndpoint = (request, response) => {
|
||||||
|
throw new ErrorHandler(401, "unknown endpoint");
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = unknownEndpoint;
|
||||||
14
middleware/verifyAdmin.js
Normal file
14
middleware/verifyAdmin.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
};
|
||||||
47
middleware/verifyToken.js
Normal file
47
middleware/verifyToken.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
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];
|
||||||
|
|
||||||
|
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);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = verifyToken;
|
||||||
64
package.json
Normal file
64
package.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "cross-env NODE_ENV=production node index",
|
||||||
|
"dev": "cross-env NODE_ENV=development && nodemon --legacy-watch",
|
||||||
|
"test": "cross-env NODE_ENV=test jest --verbose --runInBand",
|
||||||
|
"test:watch": "cross-env NODE_ENV=test jest --verbose --runInBand --watch",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"coveragePathIgnorePatterns": [
|
||||||
|
"/node_modules/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-async-errors": "^3.1.1",
|
||||||
|
"google-auth-library": "^8.7.0",
|
||||||
|
"googleapis": "^112.0.0",
|
||||||
|
"helmet": "^4.4.1",
|
||||||
|
"joi": "^17.13.3",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"mqtt": "^5.14.0",
|
||||||
|
"mssql": "^11.0.1",
|
||||||
|
"multer": "^1.4.5-lts.2",
|
||||||
|
"nodemailer": "^6.8.0",
|
||||||
|
"pg": "^8.8.0",
|
||||||
|
"pino": "^6.11.3",
|
||||||
|
"stripe": "^8.138.0",
|
||||||
|
"svg-captcha": "^1.4.0",
|
||||||
|
"swagger-ui-express": "^4.6.0",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"pino-pretty": "^4.8.0",
|
||||||
|
"prettier": "^2.8.1",
|
||||||
|
"supertest": "^6.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
readme.md
Normal file
7
readme.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
touch README.md
|
||||||
|
git init
|
||||||
|
git checkout -b main
|
||||||
|
git add README.md
|
||||||
|
git commit -m "first commit"
|
||||||
|
git remote add origin https://gitea.idetama.id/yogiedigital/cod-api.git
|
||||||
|
git push -u origin main
|
||||||
8
routes/auth.js
Normal file
8
routes/auth.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const router = require("express").Router();
|
||||||
|
const {
|
||||||
|
loginUser,
|
||||||
|
} = require("../controllers/auth.controller");
|
||||||
|
|
||||||
|
router.post("/login", loginUser);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
8
routes/index.js
Normal file
8
routes/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const router = require("express").Router();
|
||||||
|
const auth = require("./auth");
|
||||||
|
const users = require("./users");
|
||||||
|
|
||||||
|
router.use("/auth", auth);
|
||||||
|
router.use("/users", users);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
33
routes/users.js
Normal file
33
routes/users.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
77
services/auth.service.js
Normal file
77
services/auth.service.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const validateUser = require("../helpers/validateUser");
|
||||||
|
const { ErrorHandler } = require("../helpers/error");
|
||||||
|
const {
|
||||||
|
getUserByUsernameDb
|
||||||
|
} = require("../db/user.db");
|
||||||
|
const { logger } = require("../utils/logger");
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
|
||||||
|
async login(username, password, tenantId) {
|
||||||
|
try {
|
||||||
|
// if (!validateUser(username, password)) {
|
||||||
|
// throw new ErrorHandler(403, "Invalid login");
|
||||||
|
// }
|
||||||
|
|
||||||
|
const user = await getUserByUsernameDb(username, tenantId);
|
||||||
|
console.log(user);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ErrorHandler(403, "Username not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCorrectPassword = password === user.password
|
||||||
|
if (!isCorrectPassword) {
|
||||||
|
throw new ErrorHandler(403, "Username or password incorrect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataToken = {
|
||||||
|
tenant_id: tenantId,
|
||||||
|
user_id: user.user_id,
|
||||||
|
username,
|
||||||
|
fullname: user.full_name,
|
||||||
|
role_id: user.role_id
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async signToken(data) {
|
||||||
|
try {
|
||||||
|
// console.log("signToken process.env.SECRET", process.env.SECRET)
|
||||||
|
return jwt.sign(data, process.env.SECRET, { expiresIn: "23h" });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
throw new ErrorHandler(500, "An error occurred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async signRefreshToken(data) {
|
||||||
|
try {
|
||||||
|
return jwt.sign(data, process.env.REFRESH_SECRET, { expiresIn: "23h" });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
throw new ErrorHandler(500, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new AuthService();
|
||||||
124
services/user.service.js
Normal file
124
services/user.service.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
const {
|
||||||
|
createUserDb,
|
||||||
|
changeUserPasswordDb,
|
||||||
|
getUserByIdDb,
|
||||||
|
updateUserDb,
|
||||||
|
deleteUserDb,
|
||||||
|
getAllUsersDb,
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
|
||||||
|
getAllStatusUsers = async () => {
|
||||||
|
try {
|
||||||
|
return statusName;
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getAllUsers = async (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
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createUser = async (param) => {
|
||||||
|
try {
|
||||||
|
const userByUsername = await getUserByUsernameDb(param.userName, param.tenantID);
|
||||||
|
|
||||||
|
if (userByUsername) {
|
||||||
|
throw new ErrorHandler(401, "username taken already");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await createUserDb(param);
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserById = async (id) => {
|
||||||
|
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 (Object.keys(errors).length > 0) {
|
||||||
|
throw new ErrorHandler(403, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateUserDb(param);
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteUser = async (id, userID) => {
|
||||||
|
try {
|
||||||
|
return await deleteUserDb(id, userID);
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getAllRoles = async (tenantID) => {
|
||||||
|
try {
|
||||||
|
return await getAllRoleDb(tenantID);
|
||||||
|
} catch (error) {
|
||||||
|
throw new ErrorHandler(error.statusCode, error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new UserService();
|
||||||
9
utils/logger.js
Normal file
9
utils/logger.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const pino = require("pino");
|
||||||
|
|
||||||
|
// Create a logging instance
|
||||||
|
const logger = pino({
|
||||||
|
level: process.env.NODE_ENV === "production" ? "info" : "debug",
|
||||||
|
prettyPrint: process.env.NODE_ENV !== "production",
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.logger = logger;
|
||||||
Reference in New Issue
Block a user