const { BeanModel } = require("redbean-node/dist/bean-model"); const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); class Maintenance extends BeanModel { /** * Return an object that ready to parse to JSON for public * Only show necessary data to public * @returns {Object} */ async toPublicJSON() { let dateRange = []; if (this.start_date) { dateRange.push(utcToLocal(this.start_date)); if (this.end_date) { dateRange.push(utcToLocal(this.end_date)); } } let timeRange = []; let startTime = timeObjectToLocal(parseTimeObject(this.start_time)); timeRange.push(startTime); let endTime = timeObjectToLocal(parseTimeObject(this.end_time)); timeRange.push(endTime); let obj = { id: this.id, title: this.title, description: this.description, strategy: this.strategy, intervalDay: this.interval_day, active: !!this.active, dateRange: dateRange, timeRange: timeRange, weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], timeslotList: [], }; const timeslotList = await this.getTimeslotList(); for (let timeslot of timeslotList) { obj.timeslotList.push(await timeslot.toPublicJSON()); } if (!Array.isArray(obj.weekdays)) { obj.weekdays = []; } if (!Array.isArray(obj.daysOfMonth)) { obj.daysOfMonth = []; } // Maintenance Status if (!obj.active) { obj.status = "inactive"; } else if (obj.strategy === "manual") { obj.status = "under-maintenance"; } else if (obj.timeslotList.length > 0) { let currentTimestamp = dayjs().unix(); for (let timeslot of obj.timeslotList) { if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) { log.debug("timeslot", "Timeslot ID: " + timeslot.id); log.debug("timeslot", "currentTimestamp:" + currentTimestamp); log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix()); log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix()); obj.status = "under-maintenance"; break; } } if (!obj.status) { obj.status = "scheduled"; } } else if (obj.timeslotList.length === 0) { obj.status = "ended"; } else { obj.status = "unknown"; } return obj; } /** * Only get future or current timeslots only * @returns {Promise<[]>} */ async getTimeslotList() { return R.convertToBeans("maintenance_timeslot", await R.getAll(` SELECT maintenance_timeslot.* FROM maintenance_timeslot, maintenance WHERE maintenance_timeslot.maintenance_id = maintenance.id AND maintenance.id = ? AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} `, [ this.id ])); } /** * Return an object that ready to parse to JSON * @param {string} timezone If not specified, the timeRange will be in UTC * @returns {Object} */ async toJSON(timezone = null) { return this.toPublicJSON(timezone); } /** * Get a list of weekdays that the maintenance is active for * Monday=1, Tuesday=2 etc. * @returns {number[]} Array of active weekdays */ getDayOfWeekList() { log.debug("timeslot", "List: " + this.weekdays); return JSON.parse(this.weekdays).sort(function (a, b) { return a - b; }); } /** * Get a list of days in month that maintenance is active for * @returns {number[]} Array of active days in month */ getDayOfMonthList() { return JSON.parse(this.days_of_month).sort(function (a, b) { return a - b; }); } /** * Get the start date and time for maintenance * @returns {dayjs.Dayjs} Start date and time */ getStartDateTime() { let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm"); log.debug("timeslot", "startOfTheDay: " + startOfTheDay); // Start Time let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second"); log.debug("timeslot", "startTime: " + startTimeSecond); // Bake StartDate + StartTime = Start DateTime return dayjs.utc(this.start_date).add(startTimeSecond, "second"); } /** * Get the duraction of maintenance in seconds * @returns {number} Duration of maintenance */ getDuration() { let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second"); // Add 24hours if it is across day if (duration < 0) { duration += 24 * 3600; } return duration; } /** * Convert data from socket to bean * @param {Bean} bean Bean to fill in * @param {Object} obj Data to fill bean with * @returns {Bean} Filled bean */ static jsonToBean(bean, obj) { if (obj.id) { bean.id = obj.id; } // Apply timezone offset to timeRange, as it cannot apply automatically. if (obj.timeRange[0]) { timeObjectToUTC(obj.timeRange[0]); if (obj.timeRange[1]) { timeObjectToUTC(obj.timeRange[1]); } } bean.title = obj.title; bean.description = obj.description; bean.strategy = obj.strategy; bean.interval_day = obj.intervalDay; bean.active = obj.active; if (obj.dateRange[0]) { bean.start_date = localToUTC(obj.dateRange[0]); if (obj.dateRange[1]) { bean.end_date = localToUTC(obj.dateRange[1]); } } bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]); bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]); bean.weekdays = JSON.stringify(obj.weekdays); bean.days_of_month = JSON.stringify(obj.daysOfMonth); return bean; } /** * SQL conditions for active maintenance * @returns {string} */ static getActiveMaintenanceSQLCondition() { return ` ( (maintenance_timeslot.start_date <= DATETIME('now') AND maintenance_timeslot.end_date >= DATETIME('now') AND maintenance.active = 1) OR (maintenance.strategy = 'manual' AND active = 1) ) `; } /** * SQL conditions for active and future maintenance * @returns {string} */ static getActiveAndFutureMaintenanceSQLCondition() { return ` ( ((maintenance_timeslot.end_date >= DATETIME('now') AND maintenance.active = 1) OR (maintenance.strategy = 'manual' AND active = 1)) ) `; } } module.exports = Maintenance;