diff --git a/config/index.js b/config/index.js index fe0b713..7c699b1 100644 --- a/config/index.js +++ b/config/index.js @@ -31,11 +31,11 @@ const poolPromise = new sql.ConnectionPool(config) async function checkConnection() { try { const pool = await poolPromise; - await pool.request().query('SELECT 1 AS isConnected'); - console.log('🔍 SQL Server terkoneksi dengan baik'); + await pool.request().query("SELECT 1 AS isConnected"); + console.log("🔍 SQL Server terkoneksi dengan baik"); return true; } catch (error) { - console.error('⚠️ Gagal cek koneksi SQL Server:', error); + console.error("⚠️ Gagal cek koneksi SQL Server:", error); return false; } } @@ -58,13 +58,16 @@ async function query(text, params = []) { return request.query(sqlText); } +/** + * Validasi tanggal + */ function isValidDate(dateStr) { const d = new Date(dateStr); - return !isNaN(d.getTime()); // true kalau valid + return !isNaN(d.getTime()); } /** - * Build filter query + * Build filter query (AND) */ function buildFilterQuery(filterQuery = [], fixedParams = []) { let whereConditions = []; @@ -76,7 +79,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) { switch (f.type) { case "string": queryParams.push(`%${f.param}%`); - whereConditions.push(`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); + whereConditions.push( + `${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS` + ); break; case "number": @@ -89,10 +94,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) { whereConditions.push(`${f.column} = $${queryParams.length}`); break; - case 'between': + case "between": if (Array.isArray(f.param) && f.param.length === 2) { - const from = f.param[0]; - const to = f.param[1]; + const [from, to] = f.param; if (isValidDate(from) && isValidDate(to)) { queryParams.push(from); queryParams.push(to); @@ -112,7 +116,7 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) { * Build OR ILIKE (SQL Server pakai LIKE + COLLATE) */ function buildStringOrIlike(columnParam, criteria, fixedParams = []) { - if (!criteria) return { whereClause: "", whereParam: fixedParams }; + if (!criteria) return { whereOrConditions: "", whereParamOr: fixedParams }; let orStringConditions = []; let queryParams = [...fixedParams]; @@ -120,7 +124,9 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) { columnParam.forEach((column) => { if (!column) return; queryParams.push(`%${criteria}%`); - orStringConditions.push(`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); + orStringConditions.push( + `${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS` + ); }); const whereClause = orStringConditions.length @@ -130,12 +136,60 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) { return { whereOrConditions: whereClause, whereParamOr: queryParams }; } +/** + * Build Date Filter (harian / mingguan / bulanan) + */ +function buildDateFilter(column, type, dateValue, fixedParams = []) { + let whereCondition = ""; + let queryParams = [...fixedParams]; + + if (!dateValue && type !== "monthly") { + return { whereDateCondition: "", whereDateParams: queryParams }; + } + + switch (type) { + case "daily": { + queryParams.push(dateValue); + whereCondition = `CAST(${column} AS DATE) = $${queryParams.length}`; + break; + } + + case "weekly": { + const startDate = new Date(dateValue); + if (!isNaN(startDate.getTime())) { + const endDate = new Date(startDate); + endDate.setDate(startDate.getDate() + 6); + + queryParams.push(startDate.toISOString().split("T")[0]); + queryParams.push(endDate.toISOString().split("T")[0]); + + whereCondition = `CAST(${column} AS DATE) BETWEEN $${queryParams.length - 1} AND $${queryParams.length}`; + } + break; + } + + case "monthly": { + const [year, month] = dateValue.split("-"); + if (year && month) { + queryParams.push(parseInt(year), parseInt(month)); + whereCondition = `YEAR(${column}) = $${queryParams.length - 1} AND MONTH(${column}) = $${queryParams.length}`; + } + break; + } + + default: + whereCondition = ""; + } + + return { whereDateCondition: whereCondition, whereDateParams: queryParams }; +} + + /** * Build dynamic UPDATE */ function buildDynamicUpdate(table, data, where) { - - data.updated_by = data.userId + data.updated_by = data.userId; delete data.userId; const setParts = []; @@ -153,7 +207,6 @@ function buildDynamicUpdate(table, data, where) { throw new Error("Tidak ada kolom untuk diupdate"); } - // updated_at otomatis pakai CURRENT_TIMESTAMP setParts.push(`updated_at = CURRENT_TIMESTAMP`); const whereParts = []; @@ -175,9 +228,8 @@ function buildDynamicUpdate(table, data, where) { * Build dynamic INSERT */ function buildDynamicInsert(table, data) { - - data.created_by = data.userId - data.updated_by = data.userId + data.created_by = data.userId; + data.updated_by = data.userId; delete data.userId; const columns = []; @@ -197,7 +249,6 @@ function buildDynamicInsert(table, data) { throw new Error("Tidak ada kolom untuk diinsert"); } - // created_at & updated_at otomatis columns.push("created_at", "updated_at"); placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"); @@ -238,6 +289,7 @@ module.exports = { checkConnection, query, buildFilterQuery, + buildDateFilter, buildStringOrIlike, buildDynamicInsert, buildDynamicUpdate, diff --git a/controllers/user_schedule.controller.js b/controllers/user_schedule.controller.js new file mode 100644 index 0000000..02f3425 --- /dev/null +++ b/controllers/user_schedule.controller.js @@ -0,0 +1,71 @@ +const UserScheduleService = require('../services/user_schedule.service'); +const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils'); +const { insertUserScheduleSchema, updateUserScheduleSchema } = require('../validate/user_schedule.schema'); + +class UserScheduleController { + // Get all User Schedule + static async getAll(req, res) { + const queryParams = req.query; + + const results = await UserScheduleService.getAllUserScheduleDb(queryParams); + const response = await setResponsePaging(queryParams, results, 'User Schedule found') + + res.status(response.statusCode).json(response); + } + + // Get User Schedule by ID + static async getById(req, res) { + const { id } = req.params; + + const results = await UserScheduleService.getUserScheduleByID(id); + const response = await setResponse(results, 'User Schedule found') + + res.status(response.statusCode).json(response); + } + + // Create User Schedule + static async create(req, res) { + const { error, value } = await checkValidate(insertUserScheduleSchema, req) + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.userId = req.user.user_id + + const results = await UserScheduleService.createUserSchedules(value); + const response = await setResponse(results, 'User Schedule created successfully') + + return res.status(response.statusCode).json(response); + } + + // Update User Schedule + static async update(req, res) { + const { id } = req.params; + + const { error, value } = checkValidate(updateUserScheduleSchema, req) + + if (error) { + return res.status(400).json(setResponse(error, 'Validation failed', 400)); + } + + value.userId = req.user.user_id + + const results = await UserScheduleService.updateUserSchedules(id, value); + const response = await setResponse(results, 'User Schedule updated successfully') + + res.status(response.statusCode).json(response); + } + + // Soft delete User Schedule + static async delete(req, res) { + const { id } = req.params; + + const results = await UserScheduleService.deleteUserSchedules(id, req.user.user_id); + const response = await setResponse(results, 'User Schedule deleted successfully') + + res.status(response.statusCode).json(response); + } +} + +module.exports = UserScheduleController; diff --git a/db/brand.db.js b/db/brand.db.js index 6182e1a..3385e8f 100644 --- a/db/brand.db.js +++ b/db/brand.db.js @@ -34,7 +34,7 @@ const getAllBrandsDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereOrConditions ? whereOrConditions : ""} ORDER BY b.brand_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/device.db.js b/db/device.db.js index c28d9a0..b2000c1 100644 --- a/db/device.db.js +++ b/db/device.db.js @@ -50,7 +50,7 @@ const getAllDevicesDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} ${whereOrConditions ? whereOrConditions : ''} ORDER BY a.device_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/roles.db.js b/db/roles.db.js index 56c42c5..17d20c5 100644 --- a/db/roles.db.js +++ b/db/roles.db.js @@ -35,7 +35,7 @@ const getAllRolesDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereOrConditions ? ` ${whereOrConditions}` : ""} ORDER BY a.role_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/schedule.db.js b/db/schedule.db.js index d3a598c..a54a0f1 100644 --- a/db/schedule.db.js +++ b/db/schedule.db.js @@ -1,7 +1,11 @@ const pool = require("../config"); const { formattedDate } = require("../utils/date"); -// Get all schedules +const normalizeClause = (clause) => { + if (!clause) return ""; + return clause.replace(/^\s*(?:AND|WHERE)\s*/i, "").trim(); +}; + const getAllScheduleDb = async (searchParams = {}) => { let queryParams = []; @@ -18,11 +22,51 @@ const getAllScheduleDb = async (searchParams = {}) => { if (whereParamOr) queryParams = whereParamOr; const { whereConditions, whereParamAnd } = pool.buildFilterQuery( - [{ column: "a.schedule_date", param: searchParams.name, type: "date" }], + [ + { + column: "a.schedule_date", + param: searchParams.name, + type: "date", + }, + ], queryParams ); if (whereParamAnd) queryParams = whereParamAnd; + const { whereDateCondition, whereDateParams } = pool.buildDateFilter( + "a.schedule_date", + searchParams.dateType, + searchParams.dateValue, + queryParams + ); + if (whereDateParams) queryParams = whereDateParams; + + const whereParts = []; + + whereParts.push("a.deleted_at IS NULL"); + + if (Array.isArray(whereConditions) && whereConditions.length > 0) { + const joined = whereConditions.join(" AND "); + const norm = normalizeClause(joined); + if (norm) whereParts.push(norm); + } else if (typeof whereConditions === "string" && whereConditions.trim()) { + const norm = normalizeClause(whereConditions); + if (norm) whereParts.push(norm); + } + + if (whereOrConditions && String(whereOrConditions).trim()) { + const norm = normalizeClause(whereOrConditions); + if (norm) whereParts.push(norm); + } + + if (whereDateCondition && String(whereDateCondition).trim()) { + const norm = normalizeClause(whereDateCondition); + if (norm) whereParts.push(norm); + } + + const whereClause = + whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : ""; + const queryText = ` SELECT COUNT(*) OVER() AS total_data, @@ -32,14 +76,13 @@ const getAllScheduleDb = async (searchParams = {}) => { b.end_time FROM schedule a LEFT JOIN m_shift b ON a.shift_id = b.shift_id - WHERE a.deleted_at IS NULL - ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} - ${whereOrConditions ? ` ${whereOrConditions}` : ""} + ${whereClause} ORDER BY a.schedule_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} + ${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) @@ -48,6 +91,7 @@ const getAllScheduleDb = async (searchParams = {}) => { return { data: result.recordset, total }; }; +// Get by ID const getScheduleByIdDb = async (id) => { const queryText = ` SELECT @@ -63,8 +107,9 @@ const getScheduleByIdDb = async (id) => { return result.recordset?.[0] || null; }; +// Insert (bisa multi hari) const insertScheduleDb = async (store) => { - const nextDays = Number(store.next_day ?? 0); // default 0 kalau tidak diisi + const nextDays = Number(store.next_day ?? 0); const insertedRecords = []; for (let i = 0; i <= nextDays; i++) { @@ -72,11 +117,7 @@ const insertScheduleDb = async (store) => { nextDate.setDate(nextDate.getDate() + i); const formatted = formattedDate(nextDate); - - const newStore = { - ...store, - schedule_date: formatted, - }; + const newStore = { ...store, schedule_date: formatted }; delete newStore.next_day; const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore); @@ -92,6 +133,7 @@ const insertScheduleDb = async (store) => { return insertedRecords; }; +// Update const updateScheduleDb = async (id, data) => { const store = { ...data }; const whereData = { schedule_id: id }; @@ -106,7 +148,7 @@ const updateScheduleDb = async (id, data) => { return getScheduleByIdDb(id); }; -// Soft delete schedule +// Soft delete const deleteScheduleDb = async (id, deletedBy) => { const queryText = ` UPDATE schedule diff --git a/db/shift.db.js b/db/shift.db.js index 4dc4192..71490a7 100644 --- a/db/shift.db.js +++ b/db/shift.db.js @@ -36,7 +36,7 @@ const getAllShiftDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereOrConditions ? ` ${whereOrConditions}` : ""} ORDER BY a.shift_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/status.db.js b/db/status.db.js index 37cbd2a..f5cd6d3 100644 --- a/db/status.db.js +++ b/db/status.db.js @@ -39,7 +39,7 @@ const getAllStatusDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} ${whereOrConditions ? whereOrConditions : ''} ORDER BY a.status_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/sub_section.db.js b/db/sub_section.db.js index e4358d7..8d40e90 100644 --- a/db/sub_section.db.js +++ b/db/sub_section.db.js @@ -37,7 +37,7 @@ const getAllSubSectionsDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereOrConditions ? whereOrConditions : ""} ORDER BY a.sub_section_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/tags.db.js b/db/tags.db.js index 41b61c9..9f47031 100644 --- a/db/tags.db.js +++ b/db/tags.db.js @@ -62,7 +62,7 @@ const getAllTagsDb = async (searchParams = {}) => { ${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` : ""} + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/unit.db.js b/db/unit.db.js index ad96fb4..3649fe2 100644 --- a/db/unit.db.js +++ b/db/unit.db.js @@ -42,7 +42,7 @@ const getAllUnitsDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""} ${whereOrConditions ? whereOrConditions : ""} ORDER BY a.unit_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/user.db.js b/db/user.db.js index c8772b6..83620cf 100644 --- a/db/user.db.js +++ b/db/user.db.js @@ -53,7 +53,7 @@ const getAllUsersDb = async (searchParams = {}) => { ${whereConditions.length > 0 ? ` AND ${whereConditions.join(' AND ')}` : ''} ${whereOrConditions ? whereOrConditions : ''} ORDER BY u.user_id ASC - ${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; + ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''} `; const result = await pool.query(queryText, queryParams); diff --git a/db/user_schedule.db.js b/db/user_schedule.db.js new file mode 100644 index 0000000..525fd61 --- /dev/null +++ b/db/user_schedule.db.js @@ -0,0 +1,116 @@ +const pool = require("../config"); + +const getAllUserScheduleDb = async (searchParams = {}) => { + let queryParams = []; + + // Handle pagination + if (searchParams.limit) { + const page = Number(searchParams.page ?? 1) - 1; + queryParams = [Number(searchParams.limit ?? 10), page]; + } + + const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike( + ["a.user_id", "a.user_schedule_id", "a.schedule_id", "a.user_schedule_id"], + searchParams.criteria, + queryParams + ); + if (whereParamOr) queryParams = whereParamOr; + + const { whereConditions, whereParamAnd } = pool.buildFilterQuery( + [ + { column: "a.user_id", param: searchParams.user_id, type: "int" }, + { column: "a.user_schedule_id", param: searchParams.user_schedule_id, type: "int" }, + { column: "a.schedule_id", param: searchParams.schedule_id, type: "int" }, + { column: "a.user_schedule_id", param: searchParams.user_schedule_id, type: "int" }, + ], + queryParams + ); + if (whereParamAnd) queryParams = whereParamAnd; + + const queryText = ` + SELECT + COUNT(*) OVER() AS total_data, + a.*, + b.schedule_date, + c.shift_name, + c.start_time, + c.end_time, + d.user_fullname + FROM user_schedule a + LEFT JOIN schedule b ON a.user_schedule_id = b.schedule_id + LEFT JOIN m_shift c ON a.user_schedule_id = c.shift_id + LEFT JOIN m_users d ON a.user_schedule_id = d.user_id + WHERE a.deleted_at IS NULL + ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} + ${whereOrConditions ? ` ${whereOrConditions}` : ""} + ORDER BY a.user_schedule_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 getUserScheduleByIdDb = async (id) => { + const queryText = ` + SELECT + a.*, + b.schedule_date, + c.shift_name, + c.start_time, + c.end_time, + d.user_fullname + FROM user_schedule a + LEFT JOIN schedule b ON a.user_schedule_id = b.schedule_id + LEFT JOIN m_shift c ON a.user_schedule_id = c.shift_id + LEFT JOIN m_users d ON a.user_schedule_id = d.user_id + WHERE a.user_schedule_id = $1 AND a.deleted_at IS NULL + `; + const result = await pool.query(queryText, [id]); + return result.recordset?.[0] || null; +}; + +const insertUserScheduleDb = async (store) => { + const { query: queryText, values } = pool.buildDynamicInsert("user_schedule", store); + const result = await pool.query(queryText, values); + const insertedId = result.recordset?.[0]?.inserted_id; + + return insertedId ? await getUserScheduleByIdDb(insertedId) : null; +}; + +const updateUserScheduleDb = async (id, data) => { + const store = { ...data }; + const whereData = { user_schedule_id: id }; + + const { query: queryText, values } = pool.buildDynamicUpdate( + "user_schedule", + store, + whereData + ); + + await pool.query(`${queryText} AND deleted_at IS NULL`, values); + return getUserScheduleByIdDb(id); +}; + +const deleteUserScheduleDb = async (id, deletedBy) => { + const queryText = ` + UPDATE user_schedule + SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1 + WHERE user_schedule_id = $2 AND deleted_at IS NULL + `; + await pool.query(queryText, [deletedBy, id]); + return true; +}; + +module.exports = { + getAllUserScheduleDb, + getUserScheduleByIdDb, + insertUserScheduleDb, + updateUserScheduleDb, + deleteUserScheduleDb, +}; diff --git a/routes/index.js b/routes/index.js index cc2c2c5..25881e1 100644 --- a/routes/index.js +++ b/routes/index.js @@ -9,6 +9,7 @@ const shift = require("./shift.route"); const schedule = require("./schedule.route"); const status = require("./status.route"); const unit = require("./unit.route") +const UserSchedule = require("./user_schedule.route") router.use("/auth", auth); router.use("/user", users); @@ -20,6 +21,7 @@ router.use("/shift", shift); router.use("/schedule", schedule); router.use("/status", status); router.use("/unit", unit); +router.use("/user-schedule", UserSchedule) module.exports = router; diff --git a/routes/user_schedule.route.js b/routes/user_schedule.route.js new file mode 100644 index 0000000..4273807 --- /dev/null +++ b/routes/user_schedule.route.js @@ -0,0 +1,17 @@ +const express = require('express'); +const UserSchedulesController = require('../controllers/user_schedule.controller'); +const verifyToken = require("../middleware/verifyToken") +const verifyAccess = require("../middleware/verifyAccess") + +const router = express.Router(); + +router.route("/") + .get(verifyToken.verifyAccessToken, UserSchedulesController.getAll) + .post(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.create); + +router.route("/:id") + .get(verifyToken.verifyAccessToken, UserSchedulesController.getById) + .put(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.update) + .delete(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.delete); + +module.exports = router; \ No newline at end of file diff --git a/services/user_schedule.service.js b/services/user_schedule.service.js new file mode 100644 index 0000000..6c07048 --- /dev/null +++ b/services/user_schedule.service.js @@ -0,0 +1,89 @@ +const { + getAllUserScheduleDb, + getUserScheduleByIdDb, + insertUserScheduleDb, + updateUserScheduleDb, + deleteUserScheduleDb + } = require('../db/user_schedule.db'); + const { ErrorHandler } = require('../helpers/error'); + + class UserScheduleService { + // Get all devices + static async getAllUserScheduleDb(param) { + try { + const results = await getAllUserScheduleDb(param); + + results.data.map(element => { + }); + + return results + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Get device by ID + static async getUserScheduleByID(id) { + try { + const result = await getUserScheduleByIdDb(id); + + if (result.length < 1) throw new ErrorHandler(404, 'User Schedule not found'); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Create device + static async createUserSchedules(data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const result = await insertUserScheduleDb(data); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Update device + static async updateUserSchedules(id, data) { + try { + if (!data || typeof data !== 'object') data = {}; + + const dataExist = await getUserScheduleByIdDb(id); + + if (dataExist.length < 1) { + throw new ErrorHandler(404, 'UserSchedules not found'); + } + + const result = await updateUserScheduleDb(id, data); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + + // Soft delete device + static async deleteUserSchedules(id, userId) { + try { + const dataExist = await getUserScheduleByIdDb(id); + + if (dataExist.length < 1) { + throw new ErrorHandler(404, 'UserSchedules not found'); + } + + const result = await deleteUserScheduleDb(id, userId); + + return result; + } catch (error) { + throw new ErrorHandler(error.statusCode, error.message); + } + } + } + + module.exports = UserScheduleService; + \ No newline at end of file diff --git a/utils/jwt.js b/utils/jwt.js index 4d5aaa4..5614be1 100644 --- a/utils/jwt.js +++ b/utils/jwt.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const tokenSettings = { access: { - expiresIn: '15m', + expiresIn: '12h', type: 'access', secret: process.env.SECRET }, diff --git a/validate/sub_section.schema.js b/validate/sub_section.schema.js index fe1e570..991f993 100644 --- a/validate/sub_section.schema.js +++ b/validate/sub_section.schema.js @@ -13,6 +13,7 @@ const insertSubSectionSchema = Joi.object({ "any.required": "Sub section name is required" }).required(), is_active: Joi.boolean().required(), + value: Joi.string().max(100).optional() }); const updateSubSectionSchema = Joi.object({ @@ -23,6 +24,7 @@ const updateSubSectionSchema = Joi.object({ "string.max": "Sub section name cannot exceed 200 characters", }).optional(), is_active: Joi.boolean().optional(), + value: Joi.string().max(100).optional() }).min(1).messages({ "object.min": "At least one field must be provided to update", }); diff --git a/validate/tags.schema.js b/validate/tags.schema.js index 29563f1..7dec8f7 100644 --- a/validate/tags.schema.js +++ b/validate/tags.schema.js @@ -12,7 +12,13 @@ const insertTagsSchema = Joi.object({ data_type: Joi.string().max(50).required(), unit: Joi.string().max(50).required(), sub_section_id: Joi.number().optional(), - is_alarm: Joi.boolean().required() + is_alarm: Joi.boolean().required(), + is_report: Joi.boolean().required(), + is_history: Joi.boolean().required(), + lim_low_crash: Joi.number().optional(), + lim_low: Joi.number().optional(), + lim_high: Joi.number().optional(), + lim_high_crash: Joi.number().optional(), }); const updateTagsSchema = Joi.object({ @@ -24,6 +30,12 @@ const updateTagsSchema = Joi.object({ unit: Joi.string().max(50), is_alarm: Joi.boolean().optional(), sub_section_id: Joi.number().optional(), + is_report: Joi.boolean().optional(), + is_history: Joi.boolean().optional(), + lim_low_crash: Joi.number().optional(), + lim_low: Joi.number().optional(), + lim_high: Joi.number().optional(), + lim_high_crash: Joi.number().optional(), }).min(1); // ✅ Export dengan CommonJS diff --git a/validate/unit.schema.js b/validate/unit.schema.js index 058e91c..dec3ff9 100644 --- a/validate/unit.schema.js +++ b/validate/unit.schema.js @@ -7,12 +7,14 @@ const insertUnitSchema = Joi.object({ unit_name: Joi.string().max(100).required(), tag_id: Joi.number().integer().optional(), is_active: Joi.boolean().required(), + description: Joi. string().max(100).optional() }); const updateUnitSchema = Joi.object({ unit_name: Joi.string().max(100).optional(), tag_id: Joi.number().integer().optional(), - is_active: Joi.boolean().optional() + is_active: Joi.boolean().optional(), + description: Joi. string().max(100).optional() }).min(1); module.exports = { diff --git a/validate/user_schedule.schema.js b/validate/user_schedule.schema.js new file mode 100644 index 0000000..7e8b6af --- /dev/null +++ b/validate/user_schedule.schema.js @@ -0,0 +1,21 @@ +const Joi = require("joi"); + +// ======================== +// User Schedule Validation +// ======================== +const insertUserScheduleSchema = Joi.object({ + user_id: Joi.number().integer().required(), + schedule_id: Joi.number().integer().required(), + shift_id: Joi.number().required(), +}); + +const updateUserScheduleSchema = Joi.object({ + user_id: Joi.number().integer().optional(), + schedule_id: Joi.number().integer().optional(), + shift_id: Joi.number().optional() +}).min(1); + +module.exports = { + insertUserScheduleSchema, + updateUserScheduleSchema +};