diff --git a/controllers/brand_sparepart.controller.js b/controllers/brand_sparepart.controller.js new file mode 100644 index 0000000..cbca77d --- /dev/null +++ b/controllers/brand_sparepart.controller.js @@ -0,0 +1,99 @@ +const BrandSparepartService = require('../services/brand_sparepart.service'); +const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils'); +const { + insertBrandSparepartSchema, + updateBrandSparepartSchema, +} = require('../validate/brand_sparepart.schema'); + +class BrandSparepartController { + static async getAll(req, res) { + const queryParams = req.query; + + const results = await BrandSparepartService.getAllBrandSparepart(queryParams); + const response = await setResponsePaging(queryParams, results, 'Brand sparepart found'); + + res.status(response.statusCode).json(response); + } + + static async getById(req, res) { + const { id } = req.params; + + const results = await BrandSparepartService.getBrandSparepartById(id); + const response = await setResponse(results, 'Brand sparepart found'); + + res.status(response.statusCode).json(response); + } + + static async create(req, res) { + const { error, value } = await checkValidate(insertBrandSparepartSchema, req); + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + try { + if (req.file) { + const file = req.file; + const ext = require('path').extname(file.originalname).toLowerCase(); + const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE"; + const folder = typeDoc === "PDF" ? "pdf" : "images"; + const pathDocument = `${folder}/${file.filename}`; + + value.path_foto = pathDocument; + } + + value.created_by = req.user?.user_id || null; + + const results = await BrandSparepartService.createBrandSparepart(value); + const response = await setResponse(results, 'Brand sparepart created successfully'); + + return res.status(response.statusCode).json(response); + } catch (err) { + const response = setResponse([], err.message, err.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + static async update(req, res) { + const { id } = req.params; + + const { error, value } = await checkValidate(updateBrandSparepartSchema, req); + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + try { + if (req.file) { + const file = req.file; + const ext = require('path').extname(file.originalname).toLowerCase(); + const typeDoc = ext === ".pdf" ? "PDF" : "IMAGE"; + const folder = typeDoc === "PDF" ? "pdf" : "images"; + const pathDocument = `${folder}/${file.filename}`; + + value.path_foto = pathDocument; + } + + value.updated_by = req.user?.user_id || null; + + const results = await BrandSparepartService.updateBrandSparepart(id, value); + const response = await setResponse(results, 'Brand sparepart updated successfully'); + + res.status(response.statusCode).json(response); + + } catch (err) { + const response = setResponse([], err.message, err.statusCode || 500); + res.status(response.statusCode).json(response); + } + } + + static async delete(req, res) { + const { id } = req.params; + + const results = await BrandSparepartService.deleteBrandSparepart(id, req.user.user_id); + const response = await setResponse(results, 'Brand sparepart deleted successfully'); + + res.status(response.statusCode).json(response); + } +} + +module.exports = BrandSparepartController; diff --git a/controllers/contact.controller.js b/controllers/contact.controller.js new file mode 100644 index 0000000..f718646 --- /dev/null +++ b/controllers/contact.controller.js @@ -0,0 +1,71 @@ +const ContactService = require('../services/contact.service'); +const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils'); +const { insertContactSchema, updateContactSchema } = require('../validate/contact.schema'); + +class ContactController { + // Get all contact + static async getAll(req, res) { + const queryParams = req.query; + + const results = await ContactService.getAllContact(queryParams); + const response = await setResponsePaging(queryParams, results, 'Contact found') + + res.status(response.statusCode).json(response); + } + + // Get contact by ID + static async getById(req, res) { + const { id } = req.params; + + const results = await ContactService.getContactById(id); + const response = await setResponse(results, 'Contact found') + + res.status(response.statusCode).json(response); + } + + // Create contact + static async create(req, res) { + const { error, value } = await checkValidate(insertContactSchema, req) + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.userId = req.user.user_id + + const results = await ContactService.createContact(value); + const response = await setResponse(results, 'Contact created successfully') + + return res.status(response.statusCode).json(response); + } + + // Update contact + static async update(req, res) { + const { id } = req.params; + + const { error, value } = checkValidate(updateContactSchema, req) + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.userId = req.user.user_id + + const results = await ContactService.updateContact(id, value); + const response = await setResponse(results, 'Contact updated successfully') + + res.status(response.statusCode).json(response); + } + + // Soft delete contact + static async delete(req, res) { + const { id } = req.params; + + const results = await ContactService.deleteContact(id, req.user.user_id); + const response = await setResponse(results, 'Contact deleted successfully') + + res.status(response.statusCode).json(response); + } +} + +module.exports = ContactController; diff --git a/controllers/notification.controller.js b/controllers/notification.controller.js new file mode 100644 index 0000000..aff649d --- /dev/null +++ b/controllers/notification.controller.js @@ -0,0 +1,80 @@ +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) { + const queryParams = req.query; + + const results = await NotificationService.getAllNotification(queryParams); + const response = await setResponsePaging(queryParams, results, 'Notification found') + + res.status(response.statusCode).json(response); + } + + + 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(" 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("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("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("Notification Error:", err.message); + return res.status(500).json(setResponse(err, 'Failed to delete notification', 500)); + } + } +} + +module.exports = NotificationController; diff --git a/db/brand_sparepart.db.js b/db/brand_sparepart.db.js new file mode 100644 index 0000000..d490dd9 --- /dev/null +++ b/db/brand_sparepart.db.js @@ -0,0 +1,126 @@ +const pool = require("../config"); + +// Get all Contact +const getAllBrandSparepartsDb = 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.sparepart_name", + "a.brand_sparepart_description", + "a.is_active" + ], + searchParams.criteria, + queryParams + ); + + if (whereParamOr) queryParams = whereParamOr; + + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "a.sparepart_name", param: searchParams.sparepart_name, type: "string" }, + { column: "a.brand_sparepart_description", param: searchParams.brand_sparepart_description, type: "string" }, + { column: "a.is_active", param: searchParams.code, type: "int" }, + ], + queryParams + ); + + if (whereParamAnd) queryParams = whereParamAnd; + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + a.* + FROM brand_sparepart a + WHERE a.deleted_at IS NULL + ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} + ${whereOrConditions ? ` ${whereOrConditions}` : ""} + ORDER BY a.brand_sparepart_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 getBrandSparepartByIdDb = async (id) => { + const queryText = ` + SELECT + a.* + FROM brand_sparepart a + WHERE a.brand_sparepart_id = $1 AND a.deleted_at IS NULL + `; + const result = await pool.query(queryText, [id]); + return result.recordset; +}; + +const createBrandSparepartDb = async (store) => { + const { query: queryText, values } = pool.buildDynamicInsert("brand_sparepart", store); + const result = await pool.query(queryText, values); + const insertedId = result.recordset?.[0]?.inserted_id; + + return insertedId ? await getBrandSparepartByIdDb(insertedId) : null; +}; + + +const updateBrandSparepartDb = async (id, data) => { + const store = { ...data }; + const whereData = { brand_sparepart_id: id }; + + const { query: queryText, values } = pool.buildDynamicUpdate( + "brand_sparepart", + store, + whereData + ); + + await pool.query(`${queryText} AND deleted_at IS NULL`, values); + return getBrandSparepartByIdDb(id); +}; + + +const deleteBrandSparepartDb = async (id, deletedBy) => { + const queryText = ` + UPDATE brand_sparepart + SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1 + WHERE brand_sparepart_id = $2 AND deleted_at IS NULL + `; + await pool.query(queryText, [deletedBy, id]); + return true; +}; + +const checkBrandSparepartNameExistsDb = async (brandSparePartName, excludeId = null) => { + let queryText = ` + SELECT brand_sparepart_id + FROM brand_sparepart + WHERE sparepart_name = $1 AND deleted_at IS NULL + `; + let values = [brandSparePartName]; + + if (excludeId) { + queryText += ` AND brand_sparepart_id != $2`; + values.push(excludeId); + } + + const result = await pool.query(queryText, values); + return result.recordset.length > 0; +}; + +module.exports = { + getAllBrandSparepartsDb, + getBrandSparepartByIdDb, + createBrandSparepartDb, + updateBrandSparepartDb, + deleteBrandSparepartDb, + checkBrandSparepartNameExistsDb +}; diff --git a/db/contact.db.js b/db/contact.db.js new file mode 100644 index 0000000..f8c3b65 --- /dev/null +++ b/db/contact.db.js @@ -0,0 +1,105 @@ +const pool = require("../config"); + +// Get all Contact +const getAllContactDb = 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.contact_name", + "a.contact_type", + ], + searchParams.criteria, + queryParams + ); + + if (whereParamOr) queryParams = whereParamOr; + + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "a.contact_name", param: searchParams.name, type: "string" }, + { column: "a.contact_type", param: searchParams.code, type: "string" }, + ], + queryParams + ); + + if (whereParamAnd) queryParams = whereParamAnd; + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + a.* + FROM contact a + WHERE a.deleted_at IS NULL + ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} + ${whereOrConditions ? ` ${whereOrConditions}` : ""} + ORDER BY a.contact_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 getContactByIdDb = async (id) => { + const queryText = ` + SELECT + a.* + FROM contact a + WHERE a.contact_id = $1 AND a.deleted_at IS NULL + `; + const result = await pool.query(queryText, [id]); + return result.recordset; +}; + +const createContactDb = async (store) => { + const { query: queryText, values } = pool.buildDynamicInsert("contact", store); + const result = await pool.query(queryText, values); + const insertedId = result.recordset?.[0]?.inserted_id; + + return insertedId ? await getContactByIdDb(insertedId) : null; +}; + +const updateContactDb = async (id, data) => { + const store = { ...data }; + const whereData = { contact_id: id }; + + const { query: queryText, values } = pool.buildDynamicUpdate( + "contact", + store, + whereData + ); + + await pool.query(`${queryText} AND deleted_at IS NULL`, values); + return getContactByIdDb(id); +}; + +// Soft delete tag +const deleteContactDb = async (id, deletedBy) => { + const queryText = ` + UPDATE contact + SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1 + WHERE contact_id = $2 AND deleted_at IS NULL + `; + await pool.query(queryText, [deletedBy, id]); + return true; +}; + +module.exports = { + getAllContactDb, + getContactByIdDb, + createContactDb, + updateContactDb, + deleteContactDb, +}; diff --git a/db/notification.db.js b/db/notification.db.js new file mode 100644 index 0000000..b745ec3 --- /dev/null +++ b/db/notification.db.js @@ -0,0 +1,149 @@ +const pool = require("../config"); + +const InsertNotificationErrorDb = async () => { + const insertQuery = ` + INSERT INTO notification_error ( + error_code_id, + is_active, + is_delivered, + is_read, + is_send, + message_error_issue + ) + SELECT + b.error_code_id, + 1 AS is_active, + 1 AS is_delivered, + 0 AS is_read, + 1 AS is_send, + + CONCAT( + COALESCE(b.error_code_description, '-'), + ' pada ', + COALESCE(d.device_name, '-'), + '. Pengecekan potensi kerusakan dibutuhkan' + ) AS message_error_issue + + FROM brand_code b + + LEFT JOIN notification_error a + ON a.error_code_id = b.error_code_id + AND a.deleted_at IS NULL + + LEFT JOIN m_device d + ON b.brand_id = d.brand_id + AND d.deleted_at IS NULL + + WHERE b.deleted_at IS NULL + AND a.notification_error_id IS NULL; + `; + + await pool.query(insertQuery); +}; + + + +const getAllNotificationDb = async (searchParams = {}) => { + let queryParams = []; + + await InsertNotificationErrorDb(); + + if (searchParams.limit) { + const page = Number(searchParams.page ?? 1) - 1; + queryParams = [Number(searchParams.limit ?? 10), page]; + } + + // Build dynamic WHERE OR + const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike( + [ + "b.error_code", + "b.error_code_name", + "c.solution_name", + "COALESCE(a.is_send, 0)", + "COALESCE(a.is_delivered, 0)", + "COALESCE(a.is_read, 0)", + "COALESCE(a.is_active, 0)" + ], + searchParams.criteria, + queryParams + ); + if (whereParamOr) queryParams = whereParamOr; + + // Build dynamic WHERE AND + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "COALESCE(a.is_send, 0)", param: searchParams.is_send, type: "int" }, + { column: "COALESCE(a.is_delivered, 0)", param: searchParams.is_delivered, type: "int" }, + { column: "COALESCE(a.is_read, 0)", param: searchParams.is_read, type: "int" }, + { column: "COALESCE(a.is_active, 0)", param: searchParams.is_active, type: "int" }, + ], + queryParams + ); + if (whereParamAnd) queryParams = whereParamAnd; + + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + + a.notification_error_id, + a.error_code_id, + a.message_error_issue, + a.is_send, + a.is_delivered, + a.is_read, + a.is_active, + + b.error_code, + b.error_code_name, + b.created_at, + + c.solution_name, + c.type_solution, + c.path_solution, + + d.device_name, + d.device_location, + + COALESCE(d.device_name, '') + ' - ' + COALESCE(b.error_code_name, '') AS device_name_error + + 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 b.error_code_id = c.error_code_id + AND c.deleted_at IS NULL + + LEFT JOIN m_device d + ON b.brand_id = d.brand_id + AND d.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 DESC + + ${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 }; +}; + +module.exports = { + getAllNotificationDb, +}; diff --git a/middleware/imagekit.js b/middleware/imagekit.js new file mode 100644 index 0000000..0c3edbd --- /dev/null +++ b/middleware/imagekit.js @@ -0,0 +1,8 @@ +const Imagekit = require("imagekit"); +const { IMAGEKIT_URL_ENDPOINT, IMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY } = process.env; + +module.exports = new Imagekit({ + publicKey: IMAGEKIT_PUBLIC_KEY, + privateKey: IMAGEKIT_PRIVATE_KEY, + urlEndpoint: IMAGEKIT_URL_ENDPOINT, +}); \ No newline at end of file diff --git a/package.json b/package.json index 0645c2a..0f41f3d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "google-auth-library": "^8.7.0", "googleapis": "^112.0.0", "helmet": "^4.4.1", + "imagekit": "^6.0.0", "joi": "^17.13.3", "jsonwebtoken": "^8.5.1", "moment": "^2.29.4", diff --git a/routes/brand_sparepart.route.js b/routes/brand_sparepart.route.js new file mode 100644 index 0000000..201e7b6 --- /dev/null +++ b/routes/brand_sparepart.route.js @@ -0,0 +1,20 @@ +const express = require('express'); +const BrandSparepartController = require('../controllers/brand_sparepart.controller'); +const verifyToken = require('../middleware/verifyToken'); +const verifyAccess = require('../middleware/verifyAccess'); +const upload = require('../middleware/uploads'); + +const router = express.Router(); + +router.route('/') + .get(verifyToken.verifyAccessToken, BrandSparepartController.getAll) + .post(verifyToken.verifyAccessToken, verifyAccess(), upload.single('path_foto') +, BrandSparepartController.create); + +router.route('/:id') + .get(verifyToken.verifyAccessToken, BrandSparepartController.getById) + .put(verifyToken.verifyAccessToken, verifyAccess(), upload.single('path_foto') +, BrandSparepartController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), BrandSparepartController.delete); + +module.exports = router; \ No newline at end of file diff --git a/routes/contact.route.js b/routes/contact.route.js new file mode 100644 index 0000000..1499c8c --- /dev/null +++ b/routes/contact.route.js @@ -0,0 +1,17 @@ +const express = require('express'); +const ContactController = require('../controllers/contact.controller'); +const verifyToken = require("../middleware/verifyToken") +const verifyAccess = require("../middleware/verifyAccess") + +const router = express.Router(); + +router.route("/") + .get(verifyToken.verifyAccessToken, ContactController.getAll) + .post(verifyToken.verifyAccessToken, verifyAccess(), ContactController.create); + +router.route("/:id") + .get(verifyToken.verifyAccessToken, ContactController.getById) + .put(verifyToken.verifyAccessToken, verifyAccess(), ContactController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), ContactController.delete); + +module.exports = router; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 5437936..880c9a7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -13,6 +13,9 @@ const status = require("./status.route"); const unit = require("./unit.route") const UserSchedule = require("./user_schedule.route") const historyValue = require("./history_value.route") +const contact = require("./contact.route") +const brandSparePart = require("./brand_sparepart.route") +const notification = require("./notification.route") router.use("/auth", auth); router.use("/user", users); @@ -28,6 +31,9 @@ router.use("/status", status); router.use("/unit", unit); router.use("/user-schedule", UserSchedule) router.use("/history", historyValue) +router.use("/contact", contact) +router.use("/brand-sparepart", brandSparePart) +router.use("/notification", notification) module.exports = router; 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/brand_sparepart.service.js b/services/brand_sparepart.service.js new file mode 100644 index 0000000..8926286 --- /dev/null +++ b/services/brand_sparepart.service.js @@ -0,0 +1,106 @@ +const { + getAllBrandSparepartsDb, + getBrandSparepartByIdDb, + createBrandSparepartDb, + updateBrandSparepartDb, + deleteBrandSparepartDb, + checkBrandSparepartNameExistsDb, +} = require('../db/brand_sparepart.db'); + +const { ErrorHandler } = require('../helpers/error'); + +class BrandSparepartService { + static async getAllBrandSparepart(param) { + try { + const results = await getAllBrandSparepartsDb(param); + results.data.map((item) => {}); + return results; + } catch (error) { + throw new ErrorHandler(error.statusCode || 500, error.message); + } + } + + static async getBrandSparepartById(id) { + try { + const brandSparepart = await getBrandSparepartByIdDb(id); + if (!brandSparepart) throw new ErrorHandler(404, 'Brand sparepart not found'); + return brandSparepart; + } catch (error) { + throw new ErrorHandler(error.statusCode || 500, error.message); + } + } + + static async createBrandSparepart(data) { + try { + if (!data || typeof data !== 'object') data = {}; + + if (data.sparepart_name) { + const exists = await checkBrandSparepartNameExistsDb(data.sparepart_name); + if (exists) throw new ErrorHandler(400, 'Brand sparepart name already exists'); + } + + const insertData = { + sparepart_name: data.sparepart_name, + brand_sparepart_description: data.brand_sparepart_description, + path_foto: data.path_foto, + error_code_id: data.error_code_id, + is_active: data.is_active, + created_by: data.created_by, + }; + + const created = await createBrandSparepartDb(insertData); + if (!created) throw new ErrorHandler(500, 'Failed to create brand sparepart'); + + return created; + } catch (error) { + throw new ErrorHandler(error.statusCode || 500, error.message); + } + } + + static async updateBrandSparepart(id, data) { + try { + const existing = await getBrandSparepartByIdDb(id); + if (!existing) throw new ErrorHandler(404, 'Brand sparepart not found'); + + if ( + data.sparepart_name && + data.sparepart_name !== existing.sparepart_name + ) { + const exists = await checkBrandSparepartNameExistsDb(data.sparepart_name, id); + if (exists) throw new ErrorHandler(400, 'Brand sparepart name already exists'); + } + + const updateData = { + sparepart_name: data.sparepart_name, + brand_sparepart_description: data.brand_sparepart_description, + path_foto: data.path_foto || existing.path_foto, + error_code_id: data.error_code_id, + is_active: data.is_active ?? existing.is_active, + updated_by: data.updated_by || null, + }; + + const updated = await updateBrandSparepartDb(id, updateData); + if (!updated) throw new ErrorHandler(500, 'Failed to update brand sparepart'); + + return await this.getBrandSparepartById(id); + } catch (error) { + throw new ErrorHandler(error.statusCode || 500, error.message); + } + } + + static async deleteBrandSparepart(id, userId) { + try { + const existing = await getBrandSparepartByIdDb(id); + if (!existing) throw new ErrorHandler(404, 'Brand sparepart not found'); + + const deleted = await deleteBrandSparepartDb(id, userId); + if (!deleted) throw new ErrorHandler(500, 'Failed to delete brand sparepart'); + + return deleted; + } catch (error) { + throw new ErrorHandler(error.statusCode || 500, error.message); + } + } +} + +module.exports = BrandSparepartService; diff --git a/services/contact.service.js b/services/contact.service.js new file mode 100644 index 0000000..9c39c47 --- /dev/null +++ b/services/contact.service.js @@ -0,0 +1,88 @@ +const { + getAllContactDb, + getContactByIdDb, + createContactDb, + updateContactDb, + deleteContactDb +} = require('../db/contact.db'); +const { ErrorHandler } = require('../helpers/error'); + +class ContactService { + // Get all Contact + static async getAllContact(param) { + try { + const results = await getAllContactDb(param); + + results.data.map(element => { + }); + + return results + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Get Contact by ID + static async getContactById(id) { + try { + const result = await getContactByIdDb(id); + + if (result.length < 1) throw new ErrorHandler(404, 'Contact not found'); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Create Contact + static async createContact(data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const result = await createContactDb(data); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Update Contact + static async updateContact(id, data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const dataExist = await getContactByIdDb(id); + + if (dataExist.length < 1) { + throw new ErrorHandler(404, 'Contact not found'); + } + + const result = await updateContactDb(id, data); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Soft delete Contact + static async deleteContact(id, userId) { + try { + const dataExist = await getContactByIdDb(id); + + if (dataExist.length < 1) { + throw new ErrorHandler(404, 'Contact not found'); + } + + const result = await deleteContactDb(id, userId); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } +} + +module.exports = ContactService; diff --git a/services/notification.service.js b/services/notification.service.js new file mode 100644 index 0000000..a7319d0 --- /dev/null +++ b/services/notification.service.js @@ -0,0 +1,85 @@ +const { + getAllNotificationDb, + getNotificationByIdDb, + insertNotificationDb, + updateNotificationDb, + deleteNotificationDb, +} = 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/uploads/images/img-1eb18866-2025-11-13_13-53-40.jpg b/uploads/images/img-1eb18866-2025-11-13_13-53-40.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-1eb18866-2025-11-13_13-53-40.jpg differ diff --git a/uploads/images/img-35ee1427-2025-11-13_14-00-21.jpg b/uploads/images/img-35ee1427-2025-11-13_14-00-21.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-35ee1427-2025-11-13_14-00-21.jpg differ diff --git a/uploads/images/img-93e423c3-2025-11-13_14-00-29.jpg b/uploads/images/img-93e423c3-2025-11-13_14-00-29.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-93e423c3-2025-11-13_14-00-29.jpg differ diff --git a/uploads/images/img-c5161c64-2025-11-13_14-07-43.jpg b/uploads/images/img-c5161c64-2025-11-13_14-07-43.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-c5161c64-2025-11-13_14-07-43.jpg differ diff --git a/uploads/images/img-c541e270-2025-11-13_13-42-29.jpg b/uploads/images/img-c541e270-2025-11-13_13-42-29.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-c541e270-2025-11-13_13-42-29.jpg differ diff --git a/uploads/images/img-c6fbc08f-2025-11-13_13-43-24.jpg b/uploads/images/img-c6fbc08f-2025-11-13_13-43-24.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-c6fbc08f-2025-11-13_13-43-24.jpg differ diff --git a/uploads/images/img-cc246c15-2025-11-13_14-07-49.jpg b/uploads/images/img-cc246c15-2025-11-13_14-07-49.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-cc246c15-2025-11-13_14-07-49.jpg differ diff --git a/uploads/images/img-d59610e1-2025-11-13_13-53-49.jpg b/uploads/images/img-d59610e1-2025-11-13_13-53-49.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-d59610e1-2025-11-13_13-53-49.jpg differ diff --git a/uploads/images/img-f377189e-2025-11-13_13-53-49.jpg b/uploads/images/img-f377189e-2025-11-13_13-53-49.jpg new file mode 100644 index 0000000..88e8a50 Binary files /dev/null and b/uploads/images/img-f377189e-2025-11-13_13-53-49.jpg differ diff --git a/uploads/pdf/pdf-467864af-2025-11-13_13-55-20.pdf b/uploads/pdf/pdf-467864af-2025-11-13_13-55-20.pdf new file mode 100644 index 0000000..3820c4d Binary files /dev/null and b/uploads/pdf/pdf-467864af-2025-11-13_13-55-20.pdf differ diff --git a/uploads/pdf/pdf-d0ded6ba-2025-10-26_17-11-14.pdf b/uploads/pdf/pdf-d0ded6ba-2025-10-26_17-11-14.pdf deleted file mode 100644 index 8da27b5..0000000 Binary files a/uploads/pdf/pdf-d0ded6ba-2025-10-26_17-11-14.pdf and /dev/null differ diff --git a/uploads/pdf/pdf-dd41285d-2025-11-13_13-38-05.pdf b/uploads/pdf/pdf-dd41285d-2025-11-13_13-38-05.pdf new file mode 100644 index 0000000..3820c4d Binary files /dev/null and b/uploads/pdf/pdf-dd41285d-2025-11-13_13-38-05.pdf differ diff --git a/uploads/pdf/pdf-ec196bab-2025-11-13_13-40-50.pdf b/uploads/pdf/pdf-ec196bab-2025-11-13_13-40-50.pdf new file mode 100644 index 0000000..3820c4d Binary files /dev/null and b/uploads/pdf/pdf-ec196bab-2025-11-13_13-40-50.pdf differ diff --git a/validate/brand_sparepart.schema.js b/validate/brand_sparepart.schema.js new file mode 100644 index 0000000..757b05a --- /dev/null +++ b/validate/brand_sparepart.schema.js @@ -0,0 +1,32 @@ +const Joi = require("joi"); + +// ======================== +// Brand Validation +// ======================== +const insertBrandSparepartSchema = Joi.object({ + sparepart_name: Joi.string().max(255).required(), + brand_sparepart_description: Joi.string().max(255).required(), + is_active: Joi.boolean().required(), + error_code_id: Joi.number().required().messages({ + "any.required": "error_code_id is required", + "number.base": "error_code_id must be a number", + }), + path_foto: Joi.string().max(255).optional().allow(''), +}); + +// Update Brand Validation +const updateBrandSparepartSchema = Joi.object({ + sparepart_name: Joi.string().max(255).required(), + brand_sparepart_description: Joi.string().max(255).required(), + is_active: Joi.boolean().optional(), + error_code_id: Joi.number().required().messages({ + "any.required": "error_code_id is required", + "number.base": "error_code_id must be a number", + }), + path_foto: Joi.string().max(255).optional().allow(''), +}); + +module.exports = { + insertBrandSparepartSchema, + updateBrandSparepartSchema, +}; diff --git a/validate/contact.schema.js b/validate/contact.schema.js new file mode 100644 index 0000000..8edcdf7 --- /dev/null +++ b/validate/contact.schema.js @@ -0,0 +1,35 @@ +const Joi = require("joi"); + +// ======================== +// Contacts Validation +// ======================== +const insertContactSchema = Joi.object({ + contact_name: Joi.string().min(3).max(100).required(), + contact_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", + }), + is_active: Joi.boolean().required(), + contact_type: Joi.string().max(255).required() +}); + +const updateContactSchema = Joi.object({ + contact_name: Joi.string().min(3).max(100).required(), + contact_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", + }), + is_active: Joi.boolean().optional(), + contact_type: Joi.string().max(255).optional() +}); + +module.exports = { + insertContactSchema, + updateContactSchema, +}; 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, +};