From 425b1ed5540d7e3aa006ce9ac4bc0eae0a8e8112 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Fri, 10 Oct 2025 20:06:56 +0700 Subject: [PATCH] add: CRUD tags --- controllers/tags.controller.js | 71 ++++++++++++++++ db/tag.db.js | 116 -------------------------- db/tags.db.js | 144 +++++++++++++++++++++++++++++++++ routes/index.js | 2 + routes/roles.route.js | 12 +-- routes/tags.route.js | 17 ++++ services/tags.service.js | 89 ++++++++++++++++++++ validate/tags.schema.js | 29 +++++++ 8 files changed, 358 insertions(+), 122 deletions(-) create mode 100644 controllers/tags.controller.js delete mode 100644 db/tag.db.js create mode 100644 db/tags.db.js create mode 100644 routes/tags.route.js create mode 100644 services/tags.service.js create mode 100644 validate/tags.schema.js diff --git a/controllers/tags.controller.js b/controllers/tags.controller.js new file mode 100644 index 0000000..fa3dc16 --- /dev/null +++ b/controllers/tags.controller.js @@ -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; diff --git a/db/tag.db.js b/db/tag.db.js deleted file mode 100644 index ed4e825..0000000 --- a/db/tag.db.js +++ /dev/null @@ -1,116 +0,0 @@ -const { query, buildFilterQuery, buildDynamicUpdate } = require("../config"); - -// Get all tags -const getAllTagsDb = async (searchParams = {}) => { - const { whereConditions, queryParams } = buildFilterQuery([ - { column: "mt.tag_name", param: searchParams.name, type: "string" }, - { column: "mt.tag_code", param: searchParams.code, type: "string" }, - { - column: "md.device_name", - param: searchParams.deviceName, - type: "string", - }, - { - column: "pss.sub_section_name", - param: searchParams.subSectionName, - type: "string", - }, - ]); - - const whereClause = whereConditions.length - ? `AND ${whereConditions.join(" AND ")}` - : ""; - - const queryText = ` - SELECT - mt.tag_id, mt.device_id, mt.tag_code, mt.tag_name, mt.tag_number, - mt.data_type, mt.unit, mt.is_active, mt.sub_section_id, - mt.created_at, mt.updated_at, mt.deleted_at, - md.device_name,md.ip_address, - pss.sub_section_code, pss.sub_section_name - FROM m_tags mt - INNER JOIN m_device md ON mt.device_id = md.device_id - INNER JOIN plant_sub_section pss ON mt.sub_section_id = pss.sub_section_id - WHERE mt.deleted_at IS NULL ${whereClause} - ORDER BY mt.tag_id ASC - `; - const result = await query(queryText, queryParams); - return result.recordset; -}; - -// Get tag by ID -const getTagByIdDb = async (id) => { - const queryText = ` - SELECT - mt.tag_id, mt.device_id, mt.tag_code, mt.tag_name, mt.tag_number, - mt.data_type, mt.unit, mt.is_active, mt.sub_section_id, - mt.created_at, mt.updated_at, mt.deleted_at, - md.device_name, - pss.sub_section_code, pss.sub_section_name - FROM m_tags mt - LEFT JOIN m_device md ON mt.device_id = md.device_id - LEFT JOIN plant_sub_section pss ON mt.sub_section_id = pss.sub_section_id - WHERE mt.tag_id = $1 AND mt.deleted_at IS NULL - `; - const result = await query(queryText, [id]); - return result.recordset[0]; -}; - -// Create tag -const createTagDb = async (data) => { - const queryText = ` - INSERT INTO m_tags - (device_id, tag_code, tag_name, tag_number, data_type, unit, is_active, sub_section_id, created_by) - VALUES - ($1,$2,$3,$4,$5,$6,$7,$8,$9); - SELECT SCOPE_IDENTITY() as tag_id; - `; - - const values = [ - data.device_id, - data.tag_code, - data.tag_name, - data.tag_number, - data.data_type, - data.unit, - data.is_active || 1, //default aktif - data.sub_section_id, - data.created_by, - ]; - - const result = await query(queryText, values); - return result.recordset[0]?.tag_id; -}; - -const updateTagDb = async (tagId, data) => { - const { query: queryText, values } = buildDynamicUpdate("m_tags", data, { - tag_id: tagId, - updated_at: "GETDATE()", - }); - const finalQuery = queryText.replace("WHERE", "WHERE deleted_at IS NULL AND"); - await query(finalQuery, values); - return true; -}; - -const deleteTagDb = async (tagId, deletedBy) => { - const queryText = ` - UPDATE m_tags - SET - deleted_at = GETDATE(), - deleted_by = $1, - is_active = 0 - WHERE tag_id = $2 - AND deleted_at IS NULL - `; - - await query(queryText, [deletedBy, tagId]); - return true; -}; - -module.exports = { - getAllTagsDb, - getTagByIdDb, - createTagDb, - updateTagDb, - deleteTagDb, -}; diff --git a/db/tags.db.js b/db/tags.db.js new file mode 100644 index 0000000..407e9eb --- /dev/null +++ b/db/tags.db.js @@ -0,0 +1,144 @@ +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; + + // Filter tambahan (AND conditions) + 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_description", + param: searchParams.device, + type: "string", + }, + { column: "b.ip_address", 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_location, + b.device_description, + c.sub_section_name + FROM m_tags a + LEFT JOIN m_device b ON a.device_id = b.device_id + LEFT JOIN 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_location, + b.device_description, + c.sub_section_name + FROM m_tags a + LEFT JOIN m_device b ON a.device_id = b.device_id + LEFT JOIN 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, +}; diff --git a/routes/index.js b/routes/index.js index 5765b89..de52665 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,10 +3,12 @@ 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") router.use("/auth", auth); router.use("/user", users); router.use("/device", device); router.use("/roles", roles); +router.use("/tags", tags) module.exports = router; diff --git a/routes/roles.route.js b/routes/roles.route.js index 44094ed..0ce2d4a 100644 --- a/routes/roles.route.js +++ b/routes/roles.route.js @@ -1,17 +1,17 @@ const express = require('express'); -const Rolesontroller = require('../controllers/roles.controller'); +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, Rolesontroller.getAll) - .post(verifyToken.verifyAccessToken, verifyAccess(), Rolesontroller.create); + .get(verifyToken.verifyAccessToken, RolesController.getAll) + .post(verifyToken.verifyAccessToken, verifyAccess(), RolesController.create); router.route("/:id") - .get(verifyToken.verifyAccessToken, Rolesontroller.getById) - .put(verifyToken.verifyAccessToken, verifyAccess(), Rolesontroller.update) - .delete(verifyToken.verifyAccessToken, verifyAccess(), Rolesontroller.delete); + .get(verifyToken.verifyAccessToken, RolesController.getById) + .put(verifyToken.verifyAccessToken, verifyAccess(), RolesController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), RolesController.delete); module.exports = router; \ No newline at end of file diff --git a/routes/tags.route.js b/routes/tags.route.js new file mode 100644 index 0000000..4160284 --- /dev/null +++ b/routes/tags.route.js @@ -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; \ No newline at end of file diff --git a/services/tags.service.js b/services/tags.service.js new file mode 100644 index 0000000..9f180ae --- /dev/null +++ b/services/tags.service.js @@ -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; + \ No newline at end of file diff --git a/validate/tags.schema.js b/validate/tags.schema.js new file mode 100644 index 0000000..c73a465 --- /dev/null +++ b/validate/tags.schema.js @@ -0,0 +1,29 @@ +// ======================== +// Device Validation + +const Joi = require("joi"); + +// ======================== +const insertTagsSchema = Joi.object({ + device_id: Joi.number().required(), + 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(), +}); + +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), +}).min(1); + +// ✅ Export dengan CommonJS +module.exports = { + insertTagsSchema, + updateTagsSchema, +};