diff --git a/controllers/notification.controller.js b/controllers/notification.controller.js new file mode 100644 index 0000000..8a8a962 --- /dev/null +++ b/controllers/notification.controller.js @@ -0,0 +1,83 @@ +const NotificationService = require('../services/notification.service'); +const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils'); +const { insertNotificationSchema, updateNotificationSchema } = require('../validate/notification.schema'); + +class NotificationController { + static async getAll(req, res) { + try { + const queryParams = req.query; + const results = await NotificationService.getAllNotification(queryParams); + const response = await setResponsePaging(queryParams, results, 'Notification list retrieved successfully'); + return res.status(response.statusCode).json(response); + } catch (err) { + console.error("❌ [getAll] Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to fetch notifications', 500)); + } + } + + + static async getById(req, res) { + try { + const { id } = req.params; + const results = await NotificationService.getNotificationById(id); + const response = await setResponse(results, 'Notification retrieved successfully'); + return res.status(response.statusCode).json(response); + } catch (err) { + console.error("❌ [getById] Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to fetch notification', 500)); + } + } + + static async create(req, res) { + try { + const { error, value } = await checkValidate(insertNotificationSchema, req); + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.created_by = req.user?.user_id || 'system'; + + const results = await NotificationService.createNotification(value); + const response = await setResponse(results, 'Notification created successfully'); + return res.status(response.statusCode).json(response); + } catch (err) { + console.error("❌ [create] Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to create notification', 500)); + } + } + + static async update(req, res) { + try { + const { id } = req.params; + const { error, value } = await checkValidate(updateNotificationSchema, req); + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.updated_by = req.user?.user_id || 'system'; + + const results = await NotificationService.updateNotification(id, value); + const response = await setResponse(results, 'Notification updated successfully'); + return res.status(response.statusCode).json(response); + } catch (err) { + console.error("❌ [update] Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to update notification', 500)); + } + } + + static async delete(req, res) { + try { + const { id } = req.params; + const results = await NotificationService.deleteNotification(id, req.user?.user_id || 'system'); + const response = await setResponse(results, 'Notification deleted successfully'); + return res.status(response.statusCode).json(response); + } catch (err) { + console.error("❌ [delete] Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to delete notification', 500)); + } + } +} + +module.exports = NotificationController; diff --git a/db/notification.db.js b/db/notification.db.js new file mode 100644 index 0000000..f90f6a1 --- /dev/null +++ b/db/notification.db.js @@ -0,0 +1,107 @@ +const pool = require("../config"); + +const getAllNotificationDb = 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.is_send", "a.is_delivered", "a.is_read", "a.is_active"], + searchParams.criteria, + queryParams + ); + if (whereParamOr) queryParams = whereParamOr; + + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "a.is_delivered", param: searchParams.is_delivered, type: "int" }, + { column: "a.is_send", param: searchParams.is_send, type: "int" }, + { column: "a.is_read", param: searchParams.is_read, type: "int" }, + { column: "a.is_active", param: searchParams.is_active, type: "int" }, + ], + queryParams + ); + if (whereParamAnd) queryParams = whereParamAnd; + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + a.*, + b.error_code, + b.error_code_name, + b.error_code_description, + c.solution_name, + c.type_solution, + c.path_solution + FROM notification_error a + LEFT JOIN brand_code b ON a.error_code_id = b.error_code_id AND b.deleted_at IS NULL + LEFT JOIN brand_code_solution c ON a.error_code_id = c.error_code_id AND c.deleted_at IS NULL + WHERE a.deleted_at IS NULL + ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} + ${whereOrConditions ? ` ${whereOrConditions}` : ""} + ORDER BY a.notification_error_id ASC + ${searchParams.limit ? `OFFSET $2 * $1 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 getNotificationByIdDb = async (id) => { + const queryText = ` + SELECT a.*, + b.error_code, + b.error_code_name, + b.error_code_description, + c.solution_name, + c.type_solution, + c.path_solution + FROM notification_error a + LEFT JOIN brand_code b ON a.error_code_id = b.error_code_id AND b.deleted_at IS NULL + LEFT JOIN brand_code_solution c ON a.error_code_id = c.error_code_id AND c.deleted_at IS NULL + WHERE a.notification_error_id = $1 AND a.deleted_at IS NULL + `; + const result = await pool.query(queryText, [id]); + return result.recordset?.[0] || null; +}; + +const insertNotificationDb = async (store) => { + const { query: queryText, values } = pool.buildDynamicInsert("notification_error", store); + const result = await pool.query(queryText, values); + const insertedId = result.recordset?.[0]?.inserted_id; + return insertedId ? await getNotificationByIdDb(insertedId) : null; +}; + +const updateNotificationDb = async (id, data) => { + const store = { ...data }; + const whereData = { notification_error_id: id }; + const { query: queryText, values } = pool.buildDynamicUpdate("notification_error", store, whereData); + await pool.query(`${queryText} AND deleted_at IS NULL`, values); + return getNotificationByIdDb(id); +}; + +const deleteNotificationDb = async (id, deletedBy) => { + const queryText = ` + UPDATE notification_error + SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1 + WHERE notification_error_id = $2 AND deleted_at IS NULL + `; + await pool.query(queryText, [deletedBy, id]); + return true; +}; + +module.exports = { + getAllNotificationDb, + getNotificationByIdDb, + insertNotificationDb, + updateNotificationDb, + deleteNotificationDb, +}; diff --git a/routes/notification.route.js b/routes/notification.route.js new file mode 100644 index 0000000..e6f1db0 --- /dev/null +++ b/routes/notification.route.js @@ -0,0 +1,23 @@ +const express = require('express'); +const NotificationController = require('../controllers/notification.controller'); +const verifyToken = require('../middleware/verifyToken'); +const verifyAccess = require('../middleware/verifyAccess'); + +const router = express.Router(); + +// =========================== +// Notification Routes +// =========================== + +router + .route('/') + .get(verifyToken.verifyAccessToken, NotificationController.getAll) + .post(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.create); + +router + .route('/:id') + .get(verifyToken.verifyAccessToken, NotificationController.getById) + .put(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), NotificationController.delete); + +module.exports = router; diff --git a/services/notification.service.js b/services/notification.service.js new file mode 100644 index 0000000..f42d48b --- /dev/null +++ b/services/notification.service.js @@ -0,0 +1,86 @@ +const { + getAllNotificationDb, + getNotificationByIdDb, + insertNotificationDb, + updateNotificationDb, + deleteNotificationDb, + handleBrandCodeError // 🧩 tambahkan ini +} = require('../db/notification.db'); + +const { ErrorHandler } = require('../helpers/error'); + +class NotificationService { + // Get all Notifications + static async getAllNotification(param) { + try { + const results = await getAllNotificationDb(param); + + results.data.map(element => { + }); + + return results; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Get notification by ID + static async getNotificationById(id) { + try { + const result = await getNotificationByIdDb(id); + + if (!result || (Array.isArray(result) && result.length < 1)) { + throw new ErrorHandler(404, 'Notification not found'); + } + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + static async createNotification(data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const result = await insertNotificationDb(data); + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + static async updateNotification(id, data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const dataExist = await getNotificationByIdDb(id); + + if (!dataExist || (Array.isArray(dataExist) && dataExist.length < 1)) { + throw new ErrorHandler(404, 'Notification not found'); + } + + const result = await updateNotificationDb(id, data); + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + static async deleteNotification(id, userId) { + try { + const dataExist = await getNotificationByIdDb(id); + + if (!dataExist || (Array.isArray(dataExist) && dataExist.length < 1)) { + throw new ErrorHandler(404, 'Notification not found'); + } + + const result = await deleteNotificationDb(id, userId); + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } +} + +module.exports = NotificationService; diff --git a/validate/notification.schema.js b/validate/notification.schema.js new file mode 100644 index 0000000..38b55fb --- /dev/null +++ b/validate/notification.schema.js @@ -0,0 +1,65 @@ +// ======================== +// Notification Validation +// ======================== + +const Joi = require("joi"); + +// ======================== +// Insert Notification Schema +// ======================== +const insertNotificationSchema = Joi.object({ + error_code_id: Joi.number().required().messages({ + "any.required": "error_code_id is required", + "number.base": "error_code_id must be a number", + }), + + is_send: Joi.boolean().required().messages({ + "any.required": "is_send is required", + "boolean.base": "is_send must be a boolean", + }), + + is_delivered: Joi.boolean().required().messages({ + "any.required": "is_delivered is required", + "boolean.base": "is_delivered must be a boolean", + }), + + is_read: Joi.boolean().required().messages({ + "any.required": "is_read is required", + "boolean.base": "is_read must be a boolean", + }), + + is_active: Joi.boolean().required().messages({ + "any.required": "is_active is required", + "boolean.base": "is_active must be a boolean", + }), +}); + +// ======================== +// Update Notification Schema +// ======================== +const updateNotificationSchema = Joi.object({ + error_code_id: Joi.number().optional().messages({ + "number.base": "error_code_id must be a number", + }), + + is_send: Joi.boolean().optional().messages({ + "boolean.base": "is_send must be a boolean", + }), + + is_delivered: Joi.boolean().optional().messages({ + "boolean.base": "is_delivered must be a boolean", + }), + + is_read: Joi.boolean().optional().messages({ + "boolean.base": "is_read must be a boolean", + }), + + is_active: Joi.boolean().optional().messages({ + "boolean.base": "is_active must be a boolean", + }), +}); + +module.exports = { + insertNotificationSchema, + updateNotificationSchema, +};