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/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/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/index.js b/routes/index.js index e6bde46..b5e7186 100644 --- a/routes/index.js +++ b/routes/index.js @@ -14,6 +14,7 @@ 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") router.use("/auth", auth); router.use("/user", users); @@ -30,6 +31,7 @@ router.use("/unit", unit); router.use("/user-schedule", UserSchedule) router.use("/history", historyValue) router.use("/contact", contact) +router.use("/brand-sparepart", brandSparePart) 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/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, +};