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/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/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..e6bde46 100644 --- a/routes/index.js +++ b/routes/index.js @@ -13,6 +13,7 @@ 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") router.use("/auth", auth); router.use("/user", users); @@ -28,6 +29,7 @@ router.use("/status", status); router.use("/unit", unit); router.use("/user-schedule", UserSchedule) router.use("/history", historyValue) +router.use("/contact", contact) module.exports = router; 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/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, +};