From 79f2a75193a83f042ddbc26e83b00a2081491b2c Mon Sep 17 00:00:00 2001 From: ksjaay Date: Fri, 6 Dec 2024 18:30:13 +0000 Subject: [PATCH] Removes caching system --- README.md | 6 +- app/context/global.js | 22 +-- docs/api/monitor.md | 9 - docs/internals/changelog.md | 71 ++++++++ package-lock.json | 15 +- package.json | 8 +- renovate.json | 4 - scripts/migrate.js | 12 +- scripts/migrations/cache-0-6-5.js | 23 +++ scripts/migrations/index.js | 2 + server/cache/certificates.js | 46 ----- server/cache/heartbeats.js | 132 -------------- server/cache/index.js | 60 +++---- server/cache/monitors.js | 170 ------------------ server/cache/notifications.js | 87 --------- server/class/certificate.js | 1 - server/class/monitor.js | 2 - server/database/queries/certificate.js | 3 +- server/database/queries/heartbeat.js | 32 +--- server/database/queries/notification.js | 4 +- server/database/sqlite/setup.js | 1 + server/index.js | 2 - server/middleware/monitor/add.js | 48 ++++- server/middleware/monitor/delete.js | 10 +- server/middleware/monitor/edit.js | 18 +- server/middleware/monitor/id.js | 10 +- server/middleware/monitor/status.js | 46 ++--- server/middleware/notifications/create.js | 13 +- server/middleware/notifications/delete.js | 4 +- server/middleware/notifications/disable.js | 4 +- server/middleware/notifications/edit.js | 4 +- server/middleware/notifications/getAll.js | 4 +- server/middleware/notifications/getUsingId.js | 10 +- server/routes/user.js | 10 +- server/utils/cron.js | 34 +--- shared/utils/propTypes.js | 2 +- shared/validators/monitor.js | 4 + test/server/classes/certificate.test.js | 2 - test/server/classes/monitor.test.js | 12 +- test/server/database/certificate.test.js | 3 +- test/server/middleware/monitor/add.test.js | 80 ++++++--- test/server/middleware/monitor/delete.test.js | 26 ++- test/server/middleware/monitor/edit.test.js | 81 +++++---- test/server/middleware/monitor/id.test.js | 32 ++-- test/server/middleware/monitor/status.test.js | 158 ++++++++++------ vite.config.js | 6 +- 46 files changed, 527 insertions(+), 806 deletions(-) delete mode 100644 renovate.json create mode 100644 scripts/migrations/cache-0-6-5.js delete mode 100644 server/cache/certificates.js delete mode 100644 server/cache/heartbeats.js delete mode 100644 server/cache/monitors.js delete mode 100644 server/cache/notifications.js diff --git a/README.md b/README.md index 7296dd2..6649ced 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ ## 🚀 Getting Started +> [!CAUTION] +> +> This project is under active development, things may randomly break. But I'll do my best to fix them as soon as possible. + #### Requirements Make sure you have the following applications installed before starting: @@ -77,7 +81,7 @@ pm2 monit ## 🎯 Roadmap - [ ] Custom status pages -- [ ] Better design for compact mode +- [x] Better design for compact mode - [ ] API keys for users - [ ] Move to Oauth2 for authentication - [ ] Allow session management (Track/logout from sessions) diff --git a/app/context/global.js b/app/context/global.js index 00ffcde..b423ce8 100644 --- a/app/context/global.js +++ b/app/context/global.js @@ -30,16 +30,6 @@ class GlobalStore { this.monitors.set(data.monitorId, data); - const nextTimeout = data.nextCheck - Date.now() + 5000; - - if (nextTimeout > 0) { - this.timeouts.set( - data.monitorId, - setTimeout(() => func(data.monitorId, this.setMonitor), nextTimeout) - ); - return true; - } - this.timeouts.set( data.monitorId, setTimeout( @@ -54,15 +44,11 @@ class GlobalStore { setTimeouts = (monitors, func) => { for (const monitor of monitors) { if (!this.timeouts.has(monitor.monitorId)) { - const nextTimeout = monitor.nextCheck - Date.now() + 5000; - const timeoutInterval = - nextTimeout > 0 ? nextTimeout : monitor.interval * 1000; - this.timeouts.set( monitor.monitorId, setTimeout( () => func(monitor.monitorId, this.setMonitor), - timeoutInterval + monitor.interval * 1000 ) ); } @@ -72,15 +58,11 @@ class GlobalStore { addMonitor = (monitor) => { this.monitors.set(monitor.monitorId, monitor); - const nextTimeout = monitor.nextCheck - Date.now() + 5000; - const timeoutInterval = - nextTimeout > 0 ? nextTimeout : monitor.interval * 1000; - this.timeouts.set( monitor.monitorId, setTimeout( () => fetchMonitorById(monitor.monitorId, this.setMonitor), - timeoutInterval + monitor.interval * 1000 ) ); }; diff --git a/docs/api/monitor.md b/docs/api/monitor.md index defae2a..7260239 100644 --- a/docs/api/monitor.md +++ b/docs/api/monitor.md @@ -126,8 +126,6 @@ There are various restrictions applied to the monitor data. The following are so | port | number | Port for the TCP monitor | | uptimePercentage | number | Uptime percentage for the monitor over the last 24 hours | | averageHeartbeatLatency | number | Average latency for the monitor over the last 24 hours | -| lastCheck | number | Time of last time monitor was checked (in ms) | -| nextCheck | number | Time of next time monitor should be checked (in ms) | | heartbeats | Array<[Heartbeat](#heartbeat-structure)> | Array of monitor heartbeats | | cert | [Certificate](#certificate-structure) | Information about the certificate | @@ -152,8 +150,6 @@ There are various restrictions applied to the monitor data. The following are so "port": null, "uptimePercentage": 83, "averageHeartbeatLatency": 38, - "lastCheck": 1708092690225, - "nextCheck": 1708095536463, "heartbeats": [ { "id": 38, @@ -180,7 +176,6 @@ There are various restrictions applied to the monitor data. The following are so "validTill": "Jul 10 03:50:39 2024 GMT", "validOn": ["lunalytics.xyz", "www.lunalytics.xyz"], "daysRemaining": "72", - "lastCheck": 1708092690225, "nextCheck": 1714178279326 } } @@ -203,8 +198,6 @@ There are various restrictions applied to the monitor data. The following are so "port": 2308, "uptimePercentage": 83, "averageHeartbeatLatency": 38, - "lastCheck": 1708092690225, - "nextCheck": 1708095536463, "heartbeats": [ { "id": 38, @@ -242,7 +235,6 @@ There are various restrictions applied to the monitor data. The following are so | validTill | date | Date the certificate expires | | validOn | array | Array of urls the certificate is valid for | | daysRemaining | number | Number of days the certificate is valid | -| lastCheck | number | Time of last time certificate was checked (in ms) | | nextCheck | number | Time of next time certificate should be checked (in ms) | ### Example Certificate @@ -255,7 +247,6 @@ There are various restrictions applied to the monitor data. The following are so "validTill": "Jul 10 03:50:39 2024 GMT", "validOn": ["lunalytics.xyz", "www.lunalytics.xyz"], "daysRemaining": "72", - "lastCheck": 1708092690225, "nextCheck": 1714178279326 } ``` diff --git a/docs/internals/changelog.md b/docs/internals/changelog.md index 8abaf7c..1e15cbf 100644 --- a/docs/internals/changelog.md +++ b/docs/internals/changelog.md @@ -1,5 +1,76 @@ # Previous updates +## 0.6.5 + +### Removes caching system + +### Summary + +Removes caching system, and adds column to certificate table for the next check time. I was testing the caching system, and realised it was pretty useless, so for now it's been removed. The difference between response time for caching and non-caching was barely noticeable. There are some calls that have a higher latency, so they may be cached in the future at somepoint. + +## 0.6.4 + +### Moves from chart.js to recharts + +## 0.6.3 + +### Adds new compact mode + +### Summary + +Adds a new compact mode that shows both the list of monitors and information about the selected monitor on the same page. This is to make it easier to filter/look through larger list of monitors. This is only available for PC/laptops and for mobile/tablet it'll still be the previous view, where the user is taken to another page. + +### Updates + +- Adds new compact mode +- Updates setting page to remove transfer ownership if you don't have the permission +- Moved scss for pages to /styles/pages directory + +## 0.6.2 + +### New config and filtering system + +### Summary + +It's a pretty small update, wanted to rework the config system because the old one was a pain to use. Also, wanted to make some more adjustments for mobile users as filtering on the monitor list page was pretty annoying. + +### New Features + +- Adds new config system +- Changes to `npm run setup` to no longer require user input (Making setup a bit easier) +- Adds `npm run setup:advance` that uses the old config setup system +- Adds a new filtering system for mobile devices, old one was uhh.. rough... +- Adds some guides for notifications + +## 0.6.1 + +### Updates package-lock.json and adds roadmap to readme + +### Summary + +Updates package-lock.json and adds roadmap to readme + +## 0.6.0 + +### Adds ability to create different notifications + +### Summary + +This PR introduces notifications for when services are down/recovered. This is the first of two parts for PRs, the second PR will be out later this month. And needs to be merged at the same time! + +### New Features + +- Allows users to create notifications +- New page to show currently available notifications +- Adds notification support for Discord, Slack, Telegram, and Webhooks +- Adds Collection which extends Map with a few extra functions +- Adds schemas for notifications (To be used in the future) + +### Updates + +- Reworks folder structure to better share code +- Reworks context store to add computed methods + ## v0.5.4 ### Cleaning up and moving to canvasjs diff --git a/package-lock.json b/package-lock.json index 6a1ef3b..55f4904 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lunalytics", - "version": "0.6.4", + "version": "0.6.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lunalytics", - "version": "0.6.4", + "version": "0.6.5", "license": "SEE LICENSE IN LICENSE", "dependencies": { "axios": "^1.6.2", @@ -52,7 +52,7 @@ "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.79.5", "vite": "^5.0.10", - "vite-plugin-compression2": "^1.0.0", + "vite-plugin-compression2": "^1.3.3", "vitepress": "^1.1.3", "vitest": "^1.5.3" } @@ -10726,13 +10726,16 @@ } }, "node_modules/vite-plugin-compression2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-compression2/-/vite-plugin-compression2-1.3.0.tgz", - "integrity": "sha512-/cYzISoYOo/SwPUBReS1E02a8eNTpQm8+lQUBj5NNGxuq4iZ3JOfWExUlobhVhPMJuejD7dipT+cMLbaWsMbdw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-compression2/-/vite-plugin-compression2-1.3.3.tgz", + "integrity": "sha512-Mb+xi/C5b68awtF4fNwRBPtoZiyUHU3I0SaBOAGlerlR31kusq1si6qG31lsjJH8T7QNg/p3IJY2HY9O9SvsfQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.1.0", "tar-mini": "^0.2.0" + }, + "peerDependencies": { + "vite": "^2.0.0||^3.0.0||^4.0.0||^5.0.0 ||^6.0.0" } }, "node_modules/vitepress": { diff --git a/package.json b/package.json index bca23b3..fae9953 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lunalytics", - "version": "0.6.4", + "version": "0.6.5", "description": "Open source Node.js server/website monitoring tool", "private": true, "author": "KSJaay ", @@ -27,8 +27,8 @@ "preview": "vite preview", "reset:password": "node ./scripts/reset.js", "server:watch": "nodemon --delay 2 --watch server --watch shared ./server/index.js", - "setup": "npm install && node ./scripts/basic_setup.js && npm run build", - "setup:advance": "npm install && node ./scripts/setup.js && npm run build", + "setup": "npm install && node ./scripts/basic_setup.js && npm run build && npm run start", + "setup:advance": "npm install && node ./scripts/setup.js && npm run build && npm run start", "start": "cross-env NODE_ENV=production node server/index.js", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", @@ -78,7 +78,7 @@ "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.79.5", "vite": "^5.0.10", - "vite-plugin-compression2": "^1.0.0", + "vite-plugin-compression2": "^1.3.3", "vitepress": "^1.1.3", "vitest": "^1.5.3" } diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 22a9943..0000000 --- a/renovate.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"] -} diff --git a/scripts/migrate.js b/scripts/migrate.js index 3b1b0c4..6801c4a 100644 --- a/scripts/migrate.js +++ b/scripts/migrate.js @@ -19,13 +19,17 @@ const migrateDatabase = async () => { const migrationListKeys = Object.keys(migrationList); - const [version, patch] = config.version.split('.'); + const [version, patch, minor] = config.version.split('.'); const validMigrations = migrationListKeys.filter((migration) => { - const [migrationVersion, migrationPatch] = migration.split('.'); + const [migrationVersion, migrationPatch, migrationMinor] = + migration.split('.'); return ( migrationVersion > version || - (migrationVersion === version && migrationPatch > patch) + (migrationVersion === version && migrationPatch > patch) || + (migrationVersion === version && + migrationPatch === patch && + migrationMinor > minor) ); }); @@ -48,6 +52,8 @@ const migrateDatabase = async () => { logger.info('MIGRATION', { message: 'Automatic migration complete' }); } + + return; }; export default migrateDatabase; diff --git a/scripts/migrations/cache-0-6-5.js b/scripts/migrations/cache-0-6-5.js new file mode 100644 index 0000000..50189e6 --- /dev/null +++ b/scripts/migrations/cache-0-6-5.js @@ -0,0 +1,23 @@ +// import local files +import SQLite from '../../server/database/sqlite/setup.js'; +import logger from '../../server/utils/logger.js'; + +const infomation = { + title: 'Removes caching system', + description: + 'Removes caching system, and adds column to certificate table for the next check time.', + version: '0.6.5', + breaking: true, +}; + +const migrate = async () => { + const client = await SQLite.connect(); + + await client.schema.alterTable('certificate', (table) => { + table.timestamp('nextCheck').defaultTo(Date.now()); + }); + + logger.info('Migrations', { message: '0.6.5 has been applied' }); +}; + +export { infomation, migrate }; diff --git a/scripts/migrations/index.js b/scripts/migrations/index.js index f43bf8b..2607b97 100644 --- a/scripts/migrations/index.js +++ b/scripts/migrations/index.js @@ -1,10 +1,12 @@ // import local files import { migrate as migrateTcpUpdate } from './tcpUpdate-0-4-0.js'; import { migrate as migrateNotifications } from './notifications-0-6-0.js'; +import { migrate as migrateCache } from './cache-0-6-5.js'; const migrationList = { '0.4.0': migrateTcpUpdate, '0.6.0': migrateNotifications, + '0.6.5': migrateCache, }; export default migrationList; diff --git a/server/cache/certificates.js b/server/cache/certificates.js deleted file mode 100644 index 3d51d95..0000000 --- a/server/cache/certificates.js +++ /dev/null @@ -1,46 +0,0 @@ -import Collection from '../../shared/utils/collection.js'; -import cleanCertificate from '../class/certificate.js'; -import { - fetchCertificate, - updateCertificate, - deleteCertificate, -} from '../database/queries/certificate.js'; - -class Certificates { - constructor() { - this.certificates = new Collection(); - } - - async get(monitorId) { - const certificate = this.certificates.get(monitorId); - - if (certificate) { - return certificate; - } - - const query = await fetchCertificate(monitorId); - - this.certificates.set(monitorId, cleanCertificate(query)); - - return query; - } - - async update(monitorId, certificate) { - delete certificate.lastCheck; - delete certificate.nextCheck; - - await updateCertificate(monitorId, certificate); - - certificate.lastCheck = Date.now(); - certificate.nextCheck = certificate.lastCheck + 86400000; - - this.certificates.set(monitorId, cleanCertificate(certificate)); - } - - async delete(monitorId) { - await deleteCertificate(monitorId); - this.certificates.delete(monitorId); - } -} - -export default Certificates; diff --git a/server/cache/heartbeats.js b/server/cache/heartbeats.js deleted file mode 100644 index 3dbf44e..0000000 --- a/server/cache/heartbeats.js +++ /dev/null @@ -1,132 +0,0 @@ -// realtime (Last 168 heartbeats) -// daily (Last 24 hours (All monitors with 5 minute gaps)) -// weekly (Last 7 days (Total of 168 monitors)) -// monthly (Last 30 days (Total of 720 monitors)) - -import Collection from '../../shared/utils/collection.js'; -import { - fetchHeartbeats, - fetchHourlyHeartbeats, - fetchDailyHeartbeats, - createHeartbeat, - deleteHeartbeats, - createHourlyHeartbeat, -} from '../database/queries/heartbeat.js'; - -class Heartbeats { - constructor() { - this.heartbeats = new Collection(); - this.dailyHeartbeats = new Collection(); - this.hourlyHeartbeats = new Collection(); - } - - async loadHeartbeats(monitors) { - for (const monitor of monitors) { - const monitorId = monitor.monitorId; - const heartbeats = await fetchHeartbeats(monitorId); - const hourlyHeartbeats = await fetchHourlyHeartbeats(monitorId); - const dailyHeartbeats = await fetchDailyHeartbeats(monitorId); - - this.heartbeats.set(monitorId, heartbeats); - this.hourlyHeartbeats.set(monitorId, hourlyHeartbeats); - this.dailyHeartbeats.set(monitorId, dailyHeartbeats); - } - } - - get(monitorId) { - const heartbeats = this.heartbeats.get(monitorId) || []; - - if (heartbeats < 12) { - return heartbeats; - } - - return heartbeats.slice(0, 12); - } - - getRealtime(monitorId) { - // Return the last 168 heartbeats - const heartbeats = this.heartbeats.get(monitorId) || []; - - return heartbeats; - } - - getDaily(monitorId) { - return this.dailyHeartbeats.get(monitorId) || []; - } - - getWeekly(monitorId) { - const heartbeats = this.hourlyHeartbeats.get(monitorId) || []; - - // 168 hours in a week - if (heartbeats.length > 168) { - return heartbeats.slice(0, 168); - } - - return heartbeats; - } - - getMonthly(monitorId) { - const heartbeats = this.hourlyHeartbeats.get(monitorId) || []; - return heartbeats; - } - - addFifthMinuteHeartbeat(monitorId, heartbeat) { - const heartbeats = this.dailyHeartbeats.get(monitorId) || []; - - // 288 heartbeats in a day (24 hours * 12 (12 = 5-minute intervals)) - if (heartbeats.length > 288) { - // remove the last heartbeat (oldest) - heartbeats.pop(); - } - - // add the new heartbeat to the beginning of the array (newest) - heartbeats.unshift(heartbeat); - - this.dailyHeartbeats.set(monitorId, heartbeats); - } - - async addHourlyHeartbeat(monitorId, heartbeat) { - const heartbeats = this.hourlyHeartbeats.get(monitorId) || []; - - const query = await createHourlyHeartbeat(heartbeat); - - // 720 hours in a month - if (heartbeats.length > 720) { - // remove the last heartbeat (oldest) - heartbeats.pop(); - } - - // add the new heartbeat to the beginning of the array (newest) - heartbeats.unshift(query); - - this.hourlyHeartbeats.set(monitorId, heartbeats); - } - - async getLastHeartbeat(monitorId) { - return fetchHeartbeats(monitorId, 1); - } - - async addHeartbeat(monitorId, heartbeat) { - const heartbeats = this.heartbeats.get(monitorId) || []; - - const databaseHeartbeat = await createHeartbeat(heartbeat); - - // 168 hours in a week - if (heartbeats.length > 168) { - // remove the last heartbeat (oldest) - heartbeats.pop(); - } - - // add the new heartbeat to the beginning of the array (newest) - heartbeats.unshift(databaseHeartbeat); - - this.heartbeats.set(monitorId, heartbeats); - } - - async delete(monitorId) { - await deleteHeartbeats(monitorId); - this.heartbeats.delete(monitorId); - } -} - -export default Heartbeats; diff --git a/server/cache/index.js b/server/cache/index.js index 771de8f..b1e6277 100644 --- a/server/cache/index.js +++ b/server/cache/index.js @@ -1,47 +1,42 @@ -// import classes -import Certificates from './certificates.js'; -import Heartbeats from './heartbeats.js'; -import Monitor from './monitors.js'; -import Notifications from './notifications.js'; +// import local files import getCertInfo from '../tools/checkCertificate.js'; import httpStatusCheck from '../tools/httpStatus.js'; import tcpStatusCheck from '../tools/tcpPing.js'; import Collection from '../../shared/utils/collection.js'; import NotificationServices from '../notifications/index.js'; import logger from '../utils/logger.js'; +import { fetchMonitor, fetchMonitors } from '../database/queries/monitor.js'; +import { + createHeartbeat, + deleteHeartbeats, + fetchHeartbeats, +} from '../database/queries/heartbeat.js'; +import { + deleteCertificate, + fetchCertificate, + updateCertificate, +} from '../database/queries/certificate.js'; +import { fetchNotificationById } from '../database/queries/notification.js'; class Master { constructor() { - this.heartbeats = new Heartbeats(); - this.certificates = new Certificates(); - this.monitors = new Monitor(); - this.notifications = new Notifications(); this.timeouts = new Collection(); } async initialise() { - await this.notifications.getAll(); - const monitors = await this.monitors.getAll(); + const monitors = await fetchMonitors(); for (const monitor of monitors) { - await this.certificates.get(monitor.monitorId); - await this.heartbeats.get(monitor.monitorId); await this.checkStatus(monitor.monitorId); } } async updateTcpStatus(monitor, heartbeat) { - const [lastHeartbeat] = await this.heartbeats.getLastHeartbeat( - monitor.monitorId - ); - await this.heartbeats.addHeartbeat(monitor.monitorId, heartbeat); + const [lastHeartbeat] = await fetchHeartbeats(monitor.monitorId, 1); + await createHeartbeat(heartbeat); clearTimeout(this.timeouts.get(monitor.monitorId)); - monitor.lastCheck = Date.now(); - monitor.nextCheck = monitor.lastCheck + monitor.interval * 1000; - await this.monitors.updateUptimePercentage(monitor); - await this.sendNotification(monitor, heartbeat, lastHeartbeat); const timeout = heartbeat.isDown ? monitor.retryInterval : monitor.interval; @@ -53,13 +48,13 @@ class Master { } async checkStatus(monitorId) { - const monitor = await this.monitors.get(monitorId).catch(() => false); + const monitor = await fetchMonitor(monitorId).catch(() => false); if (!monitor) { clearTimeout(this.timeouts.get(monitorId)); this.timeouts.delete(monitorId); - await this.certificates.delete(monitorId); - await this.heartbeats.delete(monitorId); + await deleteCertificate(monitorId); + await deleteHeartbeats(monitorId); return; } @@ -68,23 +63,20 @@ class Master { } if (monitor.type === 'http') { - const [lastHeartbeat] = await this.heartbeats.getLastHeartbeat(monitorId); + const [lastHeartbeat] = await fetchHeartbeats(monitorId, 1); const heartbeat = await httpStatusCheck(monitor); - await this.heartbeats.addHeartbeat(monitorId, heartbeat); + await createHeartbeat(heartbeat); if (monitor.url?.toLowerCase().startsWith('https')) { - const certificate = await this.certificates.get(monitorId); + const certificate = await fetchCertificate(monitorId); if (!certificate?.nextCheck || certificate.nextCheck <= Date.now()) { const cert = await getCertInfo(monitor.url); - await this.certificates.update(monitorId, cert); + cert.nextCheck = Date.now() + 600000; + await updateCertificate(monitorId, cert); } } - monitor.lastCheck = Date.now(); - monitor.nextCheck = monitor.lastCheck + monitor.interval * 1000; - await this.monitors.updateUptimePercentage(monitor); - await this.sendNotification(monitor, heartbeat, lastHeartbeat); const timeout = heartbeat.isDown @@ -126,9 +118,7 @@ class Master { if (!hasOutage && !hasRecovered) return; if (!monitor.notificationId) return; - const notification = await this.notifications.getById( - monitor.notificationId - ); + const notification = await fetchNotificationById(monitor.notificationId); if ( !notification?.isEnabled || diff --git a/server/cache/monitors.js b/server/cache/monitors.js deleted file mode 100644 index 0486347..0000000 --- a/server/cache/monitors.js +++ /dev/null @@ -1,170 +0,0 @@ -import Collection from '../../shared/utils/collection.js'; -import { cleanPartialMonitor } from '../class/monitor.js'; -import { - fetchMonitor, - fetchMonitors, - createMonitor, - updateMonitor, - deleteMonitor, - fetchUptimePercentage, -} from '../database/queries/monitor.js'; - -class Monitor { - constructor() { - this.monitors = new Collection(); - } - - async get(monitorId) { - const monitor = this.monitors.get(monitorId); - - if (monitor) { - return monitor; - } - - const query = await fetchMonitor(monitorId); - - this.monitors.set(monitorId, query); - - return query; - } - - async getAll() { - if (this.monitors.size) { - return this.monitors.toJSONValues(); - } - - const query = await fetchMonitors(); - - for (const monitor of query) { - this.monitors.set(monitor.monitorId, cleanPartialMonitor(monitor)); - } - - return query; - } - - async getKeys() { - if (this.monitors.size) { - return this.monitors.keys(); - } - - const query = await fetchMonitors(); - - for (const monitor of query) { - this.monitors.set(monitor.monitorId, monitor); - } - - return this.monitors.keys(); - } - - async addOrEdit(body, email, isHttp, isEdit) { - const databaseFunction = isEdit ? updateMonitor : createMonitor; - - if (isHttp) { - const { - name, - url, - method, - valid_status_codes, - interval, - retryInterval, - requestTimeout, - notificationId, - notificationType, - } = body; - - const monitor = { - name, - url, - method, - interval, - retryInterval, - requestTimeout, - notificationId, - notificationType, - valid_status_codes: JSON.stringify(valid_status_codes), - email, - type: 'http', - }; - - if (isEdit) { - monitor.monitorId = body.monitorId; - } - - const data = await databaseFunction(monitor); - const monitorData = cleanPartialMonitor({ - ...data, - uptimePercentage: 0, - averageHeartbeatLatency: 0, - }); - - this.monitors.set(monitorData.monitorId, monitorData); - - return monitorData; - } else { - const { - name, - url, - port, - interval, - retryInterval, - requestTimeout, - notificationId, - notificationType, - } = body; - - const monitor = { - name, - url, - port, - interval, - retryInterval, - requestTimeout, - notificationId, - notificationType, - valid_status_codes: '', - email, - type: 'tcp', - }; - - if (isEdit) { - monitor.monitorId = body.monitorId; - } - - const data = await databaseFunction(monitor); - const monitorData = cleanPartialMonitor({ - ...data, - uptimePercentage: 0, - averageHeartbeatLatency: 0, - }); - - this.monitors.set(monitorData.monitorId, monitorData); - - return monitorData; - } - } - - async updateUptimePercentage(monitor) { - if (!monitor) { - return; - } - - const { uptimePercentage, averageHeartbeatLatency } = - await fetchUptimePercentage(monitor.monitorId); - - monitor.uptimePercentage = uptimePercentage; - monitor.averageHeartbeatLatency = averageHeartbeatLatency; - - this.monitors.set(monitor.monitorId, monitor); - } - - setInCache(monitorId, monitor) { - this.monitors.set(monitorId, monitor); - } - - async delete(monitorId) { - await deleteMonitor(monitorId); - this.monitors.delete(monitorId); - } -} - -export default Monitor; diff --git a/server/cache/notifications.js b/server/cache/notifications.js deleted file mode 100644 index a82d120..0000000 --- a/server/cache/notifications.js +++ /dev/null @@ -1,87 +0,0 @@ -import Collection from '../../shared/utils/collection.js'; -import logger from '../utils/logger.js'; -import { - createNotification, - deleteNotification, - editNotification, - fetchNotificationById, - fetchNotifications, - fetchNotificationUniqueId, - toggleNotification, -} from '../database/queries/notification.js'; - -class Notifications { - constructor() { - this.notifications = new Collection(); - } - - async getAll() { - if (this.notifications.size) { - return this.notifications.toJSONValues(); - } - - const notifications = await fetchNotifications(); - for (const notification of notifications) { - this.notifications.set(notification.id, notification); - } - - return notifications; - } - - async getById(id) { - if (!id) return; - - const notification = this.notifications.get(id); - - if (notification) { - return notification; - } - - const query = await fetchNotificationById(id); - - if (!query) { - logger.error('Notification - getById', { - id, - message: 'Notification does not exist', - }); - return null; - } - - this.notifications.set(id, query); - - return query; - } - - async create(notification, email) { - const uniqueId = await fetchNotificationUniqueId(); - - const query = await createNotification({ - ...notification, - email, - id: uniqueId, - isEnabled: true, - }); - - this.notifications.set(uniqueId, query); - return query; - } - - async edit(notification) { - const query = await editNotification(notification); - - this.notifications.set(notification.id, query); - return query; - } - - async toggle(id, enabled) { - await toggleNotification(id, enabled); - return; - } - - async delete(id) { - await deleteNotification(id); - this.notifications.delete(id); - } -} - -export default Notifications; diff --git a/server/class/certificate.js b/server/class/certificate.js index 93831e8..3f7783e 100644 --- a/server/class/certificate.js +++ b/server/class/certificate.js @@ -13,7 +13,6 @@ const cleanCertificate = (certificate) => ({ validTill: certificate.validTill, validOn: parseJson(certificate.validOn), daysRemaining: certificate.daysRemaining, - lastCheck: certificate.lastCheck, nextCheck: certificate.nextCheck, }); diff --git a/server/class/monitor.js b/server/class/monitor.js index 129b81d..9a2c386 100644 --- a/server/class/monitor.js +++ b/server/class/monitor.js @@ -46,8 +46,6 @@ export const cleanMonitor = ({ heartbeats = [], cert, ...monitor }) => ({ notificationType: monitor.notificationType, uptimePercentage: monitor.uptimePercentage, averageHeartbeatLatency: monitor.averageHeartbeatLatency, - lastCheck: monitor.lastCheck, - nextCheck: monitor.nextCheck, cert: !cert?.isValid ? cert : cleanCertificate(cert), heartbeats, }); diff --git a/server/database/queries/certificate.js b/server/database/queries/certificate.js index 9d7216a..dc89a57 100644 --- a/server/database/queries/certificate.js +++ b/server/database/queries/certificate.js @@ -1,3 +1,4 @@ +import cleanCertificate from '../../class/certificate.js'; import SQLite from '../sqlite/setup.js'; export const fetchCertificate = async (monitorId) => { @@ -9,7 +10,7 @@ export const fetchCertificate = async (monitorId) => { return { isValid: false }; } - return certificate; + return cleanCertificate(certificate); }; export const updateCertificate = async (monitorId, certificate) => { diff --git a/server/database/queries/heartbeat.js b/server/database/queries/heartbeat.js index b121334..302dcdd 100644 --- a/server/database/queries/heartbeat.js +++ b/server/database/queries/heartbeat.js @@ -20,34 +20,6 @@ export const fetchHeartbeatsByDate = async (monitorId, date) => { return heartbeats; }; -export const fetchLastDailyHeartbeat = async (monitorId) => { - const currentDate = Date.now(); - const date = currentDate - (currentDate % 300000) - 300000; - - const heartbeats = await SQLite.client('heartbeat') - .where({ monitorId, isDown: false }) - .andWhere('date', '>', date) - .orderBy('date', 'desc'); - - if (heartbeats.length === 0) { - return { - date: date, - status: 'down', - latency: 0, - }; - } - - const averageLatency = Math.round( - heartbeats.reduce((acc, curr) => acc + curr.latency, 0) / heartbeats.length - ); - - return { - date: date, - status: heartbeats[0].status, - latency: averageLatency, - }; -}; - export const fetchDailyHeartbeats = async (monitorId) => { const date = Date.now() - 86400000; @@ -93,12 +65,12 @@ export const fetchDailyHeartbeats = async (monitorId) => { return dailyHeartbeats; }; -export const fetchHourlyHeartbeats = async (monitorId) => { +export const fetchHourlyHeartbeats = async (monitorId, limit = 720) => { const heartbeats = await SQLite.client('hourly_heartbeat') .where({ monitorId }) .select('id', 'status', 'latency', 'date') .orderBy('date', 'desc') - .limit(720); + .limit(limit); return heartbeats; }; diff --git a/server/database/queries/notification.js b/server/database/queries/notification.js index fc7112f..fcf40b6 100644 --- a/server/database/queries/notification.js +++ b/server/database/queries/notification.js @@ -58,6 +58,8 @@ export const toggleNotification = async (id, isEnabled = true) => { export const deleteNotification = async (id) => { await SQLite.client('notifications').where({ id }).del(); - await SQLite.client('monitor').where({ id }).update({ notificationId: null }); + await SQLite.client('monitor') + .where({ notificationId: id }) + .update({ notificationId: null }); return; }; diff --git a/server/database/sqlite/setup.js b/server/database/sqlite/setup.js index 9690413..3b74174 100644 --- a/server/database/sqlite/setup.js +++ b/server/database/sqlite/setup.js @@ -171,6 +171,7 @@ export class SQLite { table.timestamp('validTill'); table.string('validOn'); table.integer('daysRemaining').defaultTo(0); + table.timestamp('nextCheck').defaultTo(Date.now()); table.index('monitorId'); }); diff --git a/server/index.js b/server/index.js index d4f689f..af89c93 100644 --- a/server/index.js +++ b/server/index.js @@ -28,8 +28,6 @@ const init = async () => { await cache.initialise(); await migrateDatabase(); - const monitors = await cache.monitors.getAll(); - await cache.heartbeats.loadHeartbeats(monitors); await initialiseCronJobs(); app diff --git a/server/middleware/monitor/add.js b/server/middleware/monitor/add.js index 7d8bf21..4433114 100644 --- a/server/middleware/monitor/add.js +++ b/server/middleware/monitor/add.js @@ -5,15 +5,48 @@ import validators from '../../../shared/validators/monitor.js'; import cache from '../../cache/index.js'; import { userExists } from '../../database/queries/user.js'; import { cleanMonitor } from '../../class/monitor.js'; +import { createMonitor } from '../../database/queries/monitor.js'; +import { fetchHeartbeats } from '../../database/queries/heartbeat.js'; +import { fetchCertificate } from '../../database/queries/certificate.js'; + +export const getTcpOrHttpData = (body, email, isHttp) => { + let monitor = { + name: body.name, + url: body.url, + interval: body.interval, + monitorId: body.monitorId, + retryInterval: body.retryInterval, + requestTimeout: body.requestTimeout, + notificationId: body.notificationId, + notificationType: body.notificationType, + email, + }; + + if (isHttp) { + monitor = { + ...monitor, + method: body.method, + valid_status_codes: JSON.stringify(body.valid_status_codes), + type: 'http', + }; + } else { + monitor = { + ...monitor, + port: body.port, + valid_status_codes: '', + type: 'tcp', + }; + } + + return monitor; +}; const monitorAdd = async (request, response) => { try { const { type } = request.body; const isHttp = type === 'http'; - const validator = isHttp ? validators.http : validators.tcp; - const isInvalidMonitor = validator(request.body); if (isInvalidMonitor) { @@ -22,16 +55,13 @@ const monitorAdd = async (request, response) => { const user = await userExists(request.cookies.access_token); - const data = await cache.monitors.addOrEdit( - request.body, - user.email, - isHttp - ); + const montior_data = getTcpOrHttpData(request.body, user.email, isHttp); + const data = await createMonitor(montior_data); await cache.checkStatus(data.monitorId); - const heartbeats = await cache.heartbeats.get(data.monitorId); - const cert = await cache.certificates.get(data.monitorId); + const heartbeats = await fetchHeartbeats(data.monitorId); + const cert = await fetchCertificate(data.monitorId); const monitor = cleanMonitor({ ...data, diff --git a/server/middleware/monitor/delete.js b/server/middleware/monitor/delete.js index d94302b..534c017 100644 --- a/server/middleware/monitor/delete.js +++ b/server/middleware/monitor/delete.js @@ -1,7 +1,9 @@ // import local files -import cache from '../../cache/index.js'; import { handleError } from '../../utils/errors.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; +import { deleteCertificate } from '../../database/queries/certificate.js'; +import { deleteHeartbeats } from '../../database/queries/heartbeat.js'; +import { deleteMonitor } from '../../database/queries/monitor.js'; const monitorDelete = async (request, response) => { try { @@ -11,9 +13,9 @@ const monitorDelete = async (request, response) => { throw new UnprocessableError('No monitorId provided'); } - await cache.monitors.delete(monitorId); - await cache.heartbeats.delete(monitorId); - await cache.certificates.delete(monitorId); + await deleteMonitor(monitorId); + await deleteHeartbeats(monitorId); + await deleteCertificate(monitorId); return response.sendStatus(200); } catch (error) { diff --git a/server/middleware/monitor/edit.js b/server/middleware/monitor/edit.js index 9ce456f..d7964f4 100644 --- a/server/middleware/monitor/edit.js +++ b/server/middleware/monitor/edit.js @@ -5,15 +5,17 @@ import validators from '../../../shared/validators/monitor.js'; import cache from '../../cache/index.js'; import { userExists } from '../../database/queries/user.js'; import { cleanMonitor } from '../../class/monitor.js'; +import { getTcpOrHttpData } from './add.js'; +import { updateMonitor } from '../../database/queries/monitor.js'; +import { fetchHeartbeats } from '../../database/queries/heartbeat.js'; +import { fetchCertificate } from '../../database/queries/certificate.js'; const monitorEdit = async (request, response) => { try { const { type } = request.body; const isHttp = type === 'http'; - const validator = isHttp ? validators.http : validators.tcp; - const isInvalidMonitor = validator(request.body); if (isInvalidMonitor) { @@ -22,17 +24,13 @@ const monitorEdit = async (request, response) => { const user = await userExists(request.cookies.access_token); - const data = await cache.monitors.addOrEdit( - request.body, - user.email, - isHttp, - true - ); + const montior_data = getTcpOrHttpData(request.body, user.email, isHttp); + const data = await updateMonitor(montior_data); await cache.checkStatus(data.monitorId); - const heartbeats = await cache.heartbeats.get(data.monitorId); - const cert = await cache.certificates.get(data.monitorId); + const heartbeats = await fetchHeartbeats(data.monitorId); + const cert = await fetchCertificate(data.monitorId); const monitor = cleanMonitor({ ...data, diff --git a/server/middleware/monitor/id.js b/server/middleware/monitor/id.js index 42c3fff..8d1fae6 100644 --- a/server/middleware/monitor/id.js +++ b/server/middleware/monitor/id.js @@ -1,7 +1,9 @@ -import cache from '../../cache/index.js'; import { cleanMonitor } from '../../class/monitor.js'; import { handleError } from '../../utils/errors.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; +import { fetchMonitor } from '../../database/queries/monitor.js'; +import { fetchCertificate } from '../../database/queries/certificate.js'; +import { fetchHeartbeats } from '../../database/queries/heartbeat.js'; const fetchMonitorUsingId = async (request, response) => { try { @@ -11,14 +13,14 @@ const fetchMonitorUsingId = async (request, response) => { throw new UnprocessableError('No monitorId provided'); } - const data = await cache.monitors.get(monitorId); + const data = await fetchMonitor(monitorId); if (!data) { return response.status(404).json({ error: 'Monitor not found' }); } - const heartbeats = await cache.heartbeats.get(data.monitorId); - const cert = await cache.certificates.get(data.monitorId); + const heartbeats = await fetchHeartbeats(data.monitorId); + const cert = await fetchCertificate(data.monitorId); const monitor = cleanMonitor({ ...data, diff --git a/server/middleware/monitor/status.js b/server/middleware/monitor/status.js index e63347a..06db661 100644 --- a/server/middleware/monitor/status.js +++ b/server/middleware/monitor/status.js @@ -1,11 +1,16 @@ -import cache from '../../cache/index.js'; import { handleError } from '../../utils/errors.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; +import { fetchMonitor } from '../../database/queries/monitor.js'; +import { + fetchDailyHeartbeats, + fetchHeartbeats, + fetchHourlyHeartbeats, +} from '../../database/queries/heartbeat.js'; const validTypes = ['latest', 'day', 'week', 'month']; const fetchMonitorStatus = async (request, response) => { try { - const { monitorId, type } = request.query; + const { monitorId, type = 'latest' } = request.query; if (!monitorId) { throw new UnprocessableError('Monitor ID is required'); @@ -15,7 +20,7 @@ const fetchMonitorStatus = async (request, response) => { throw new UnprocessableError('Invalid type'); } - const monitorExists = await cache.monitors.get(monitorId); + const monitorExists = await fetchMonitor(monitorId); if (!monitorExists) { return response.status(404).json({ @@ -23,43 +28,28 @@ const fetchMonitorStatus = async (request, response) => { }); } - if (type === 'latest') { - const heartbeats = cache.heartbeats.getRealtime(monitorId); + let heartbeats = []; - return response.json(heartbeats); + if (type === 'latest') { + heartbeats = await fetchHeartbeats(monitorId); } - if (type === 'day') { - const heartbeats = cache.heartbeats.getDaily(monitorId); - - if (heartbeats.length < 2) { - return response.sendStatus(416); - } - - return response.json(heartbeats); + heartbeats = await fetchDailyHeartbeats(monitorId); } if (type === 'week') { - const heartbeats = cache.heartbeats.getWeekly(monitorId); - - if (heartbeats.length < 2) { - return response.sendStatus(416); - } - - return response.json(heartbeats); + heartbeats = await fetchHourlyHeartbeats(monitorId, 168); } if (type === 'month') { - const heartbeats = cache.heartbeats.getMonthly(monitorId); - - if (heartbeats.length < 2) { - return response.sendStatus(416); - } + heartbeats = await fetchHourlyHeartbeats(monitorId, 720); + } - return response.json(heartbeats); + if (type !== 'latest' && heartbeats.length < 2) { + return response.sendStatus(416); } - return response.sendStatus(404); + return response.json(heartbeats); } catch (error) { return handleError(error, response); } diff --git a/server/middleware/notifications/create.js b/server/middleware/notifications/create.js index d11780b..ecb2ba0 100644 --- a/server/middleware/notifications/create.js +++ b/server/middleware/notifications/create.js @@ -2,7 +2,10 @@ import { handleError } from '../../utils/errors.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; import NotificationValidators from '../../../shared/validators/notifications/index.js'; import { userExists } from '../../database/queries/user.js'; -import cache from '../../cache/index.js'; +import { + createNotification, + fetchNotificationUniqueId, +} from '../../database/queries/notification.js'; const NotificationCreateMiddleware = async (request, response) => { const notification = request.body; @@ -18,7 +21,13 @@ const NotificationCreateMiddleware = async (request, response) => { const user = await userExists(request.cookies.access_token); - const query = await cache.notifications.create(result, user.email); + const uniqueId = await fetchNotificationUniqueId(); + const query = await createNotification({ + ...result, + email: user.email, + id: uniqueId, + isEnabled: true, + }); return response.status(201).send(query); } catch (error) { diff --git a/server/middleware/notifications/delete.js b/server/middleware/notifications/delete.js index 719749b..26523e1 100644 --- a/server/middleware/notifications/delete.js +++ b/server/middleware/notifications/delete.js @@ -1,6 +1,6 @@ import { handleError } from '../../utils/errors.js'; -import cache from '../../cache/index.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; +import { deleteNotification } from '../../database/queries/notification.js'; const NotificationDeleteMiddleware = async (request, response) => { const { notificationId } = request.query; @@ -10,7 +10,7 @@ const NotificationDeleteMiddleware = async (request, response) => { } try { - await cache.notifications.delete(notificationId); + await deleteNotification(notificationId); return response.status(200).send('Notification deleted'); } catch (error) { handleError(error, response); diff --git a/server/middleware/notifications/disable.js b/server/middleware/notifications/disable.js index e407d2c..90611b5 100644 --- a/server/middleware/notifications/disable.js +++ b/server/middleware/notifications/disable.js @@ -1,6 +1,6 @@ import { handleError } from '../../utils/errors.js'; -import cache from '../../cache/index.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; +import { toggleNotification } from '../../database/queries/notification.js'; const NotificationToggleMiddleware = async (request, response) => { const { notificationId, isEnabled } = request.query; @@ -14,7 +14,7 @@ const NotificationToggleMiddleware = async (request, response) => { } try { - await cache.notifications.toggle(notificationId, isEnabled === 'true'); + await toggleNotification(notificationId, isEnabled === 'true'); return response.sendStatus(200); } catch (error) { handleError(error, response); diff --git a/server/middleware/notifications/edit.js b/server/middleware/notifications/edit.js index 977271a..88cd723 100644 --- a/server/middleware/notifications/edit.js +++ b/server/middleware/notifications/edit.js @@ -1,7 +1,7 @@ import { handleError } from '../../utils/errors.js'; import { UnprocessableError } from '../../../shared/utils/errors.js'; import NotificationValidators from '../../../shared/validators/notifications/index.js'; -import cache from '../../cache/index.js'; +import { editNotification } from '../../database/queries/notification.js'; const NotificationEditMiddleware = async (request, response) => { const notification = request.body; @@ -15,7 +15,7 @@ const NotificationEditMiddleware = async (request, response) => { const result = validator({ ...notification, ...notification.data }); - const query = await cache.notifications.edit({ + const query = await editNotification({ ...result, id: notification.id, email: notification.email, diff --git a/server/middleware/notifications/getAll.js b/server/middleware/notifications/getAll.js index ad74ecf..6c52247 100644 --- a/server/middleware/notifications/getAll.js +++ b/server/middleware/notifications/getAll.js @@ -1,9 +1,9 @@ import { handleError } from '../../utils/errors.js'; -import cache from '../../cache/index.js'; +import { fetchNotifications } from '../../database/queries/notification.js'; const NotificationGetAllMiddleware = async (request, response) => { try { - const notifications = await cache.notifications.getAll(); + const notifications = await fetchNotifications(); return response.json(notifications); } catch (error) { diff --git a/server/middleware/notifications/getUsingId.js b/server/middleware/notifications/getUsingId.js index ae7508c..c4ac2ee 100644 --- a/server/middleware/notifications/getUsingId.js +++ b/server/middleware/notifications/getUsingId.js @@ -1,13 +1,19 @@ import { handleError } from '../../utils/errors.js'; -import cache from '../../cache/index.js'; +import { fetchNotificationById } from '../../database/queries/notification.js'; +import logger from '../../utils/logger.js'; const NotificationGetUsingIdMiddleware = async (request, response) => { const { notificationId } = request.query; try { - const notification = await cache.notifications.getById(notificationId); + const notification = await fetchNotificationById(notificationId); if (!notification) { + logger.error('Notification - getById', { + notificationId, + message: 'Notification does not exist', + }); + return response.status(404).send({ message: 'Notification not found', }); diff --git a/server/routes/user.js b/server/routes/user.js index fff7e60..becfb83 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -1,7 +1,6 @@ import express from 'express'; const router = express.Router(); -import cache from '../cache/index.js'; import hasAdminPermissions from '../middleware/user/hasAdmin.js'; import accessDeclineMiddleware from '../middleware/user/access/declineUser.js'; import accessApproveMiddleware from '../middleware/user/access/approveUser.js'; @@ -16,6 +15,9 @@ import userUpdatePassword from '../middleware/user/update/password.js'; import transferOwnershipMiddleware from '../middleware/user/transferOwnership.js'; import deleteAccountMiddleware from '../middleware/user/deleteAccount.js'; import { handleError } from '../utils/errors.js'; +import { fetchMonitors } from '../database/queries/monitor.js'; +import { fetchHeartbeats } from '../database/queries/heartbeat.js'; +import { fetchCertificate } from '../database/queries/certificate.js'; router.get('/', async (request, response) => { try { @@ -56,17 +58,17 @@ router.post('/exists', async (request, response) => { router.get('/monitors', async (request, response) => { try { - const monitors = await cache.monitors.getAll(); + const monitors = await fetchMonitors(); const query = []; for (const monitor of monitors) { - const heartbeats = await cache.heartbeats.get(monitor.monitorId); + const heartbeats = await fetchHeartbeats(monitor.monitorId, 12); monitor.heartbeats = heartbeats; monitor.cert = { isValid: false }; if (monitor.type === 'http') { - const cert = await cache.certificates.get(monitor.monitorId); + const cert = await fetchCertificate(monitor.monitorId); monitor.cert = cert; } diff --git a/server/utils/cron.js b/server/utils/cron.js index f758d86..3a80927 100644 --- a/server/utils/cron.js +++ b/server/utils/cron.js @@ -2,12 +2,12 @@ import { CronJob } from 'cron'; // import local files -import cache from '../cache/index.js'; import logger from '../utils/logger.js'; import { + createHourlyHeartbeat, fetchHeartbeatsByDate, - fetchLastDailyHeartbeat, } from '../database/queries/heartbeat.js'; +import { fetchMonitors } from '../database/queries/monitor.js'; // fetch all monitors // fetch only heartbeats that are up for each monitor @@ -25,7 +25,8 @@ async function initialiseCronJobs() { message: 'Running hourly cron job for creating heartbeat', }); - const monitors = await cache.monitors.getKeys(); + const monitorsList = await fetchMonitors(); + const monitors = Object.keys(monitorsList); if (monitors.length === 0) { return; @@ -62,32 +63,7 @@ async function initialiseCronJobs() { latency: averageLatency, }; - await cache.heartbeats.addHourlyHeartbeat(monitorId, data); - } - }, - null, - true, - 'Europe/London' - ); - - // every 5 minutes - new CronJob( - '*/5 * * * *', - async function () { - logger.info('Cron', { - message: 'Running 5 minute cron job for fetching heartbeats', - }); - - const monitors = await cache.monitors.getKeys(); - - if (monitors.length === 0) { - return; - } - - for (const monitorId of monitors) { - const query = await fetchLastDailyHeartbeat(monitorId); - - cache.heartbeats.addFifthMinuteHeartbeat(monitorId, query); + await createHourlyHeartbeat(data); } }, null, diff --git a/shared/utils/propTypes.js b/shared/utils/propTypes.js index 37e0bb7..6c8f1ff 100644 --- a/shared/utils/propTypes.js +++ b/shared/utils/propTypes.js @@ -24,7 +24,6 @@ const monitorPropType = PropTypes.shape({ body: PropTypes.string, valid_status_codes: PropTypes.array.isRequired, email: PropTypes.string.isRequired, - nextCheck: PropTypes.number.isRequired, }); const heartbeatPropType = PropTypes.shape({ @@ -45,6 +44,7 @@ const certPropType = PropTypes.shape({ validTill: PropTypes.string.isRequired, validOn: PropTypes.string.isRequired, daysRemaining: PropTypes.number.isRequired, + nextCheck: PropTypes.number.isRequired, }); const fullMonitorPropType = PropTypes.shape({ diff --git a/shared/validators/monitor.js b/shared/validators/monitor.js index a56f879..8eb3152 100644 --- a/shared/validators/monitor.js +++ b/shared/validators/monitor.js @@ -28,6 +28,10 @@ export const httpUrl = (url) => { if (!url || !urlRegex.test(url)) { return 'Please enter a valid URL.'; } + + if (!url.includes('http://') && !url.includes('https://')) { + return 'Please enter a valid URL. Only http:// or https:// is allowed.'; + } }; export const httpMethod = (method) => { diff --git a/test/server/classes/certificate.test.js b/test/server/classes/certificate.test.js index abf0e65..6316c82 100644 --- a/test/server/classes/certificate.test.js +++ b/test/server/classes/certificate.test.js @@ -9,7 +9,6 @@ describe('Certificate - Class', () => { validTill: 'Jul 14 01:56:21 2024 GMT', validOn: JSON.stringify(['*.vercel.app', 'vercel.app']), daysRemaining: 62, - lastCheck: 1715559831877, nextCheck: 1715646231877, }; @@ -20,7 +19,6 @@ describe('Certificate - Class', () => { validTill: 'Jul 14 01:56:21 2024 GMT', validOn: ['*.vercel.app', 'vercel.app'], daysRemaining: 62, - lastCheck: 1715559831877, nextCheck: 1715646231877, }); }); diff --git a/test/server/classes/monitor.test.js b/test/server/classes/monitor.test.js index bb87dca..ef86567 100644 --- a/test/server/classes/monitor.test.js +++ b/test/server/classes/monitor.test.js @@ -17,6 +17,8 @@ describe('Monitor - Class', () => { body: null, valid_status_codes: JSON.stringify(['200-299']), email: 'ksjaay@lunalytics.xyz', + notificationId: 'test', + notificationType: 'All', type: 'http', port: null, uptimePercentage: 100, @@ -30,7 +32,6 @@ describe('Monitor - Class', () => { validTill: 'Jul 14 01:56:21 2024 GMT', validOn: JSON.stringify(['*.vercel.app', 'vercel.app']), daysRemaining: 62, - lastCheck: 1715559831877, nextCheck: 1715646231877, }; @@ -47,6 +48,8 @@ describe('Monitor - Class', () => { body: null, valid_status_codes: ['200-299'], email: 'ksjaay@lunalytics.xyz', + notificationId: 'test', + notificationType: 'All', type: 'http', port: null, uptimePercentage: 100, @@ -58,8 +61,6 @@ describe('Monitor - Class', () => { expect( cleanMonitor({ ...monitor, - lastCheck: 1715559831877, - nextCheck: 1715646231877, cert: certificate, heartbeats: [], }) @@ -75,12 +76,12 @@ describe('Monitor - Class', () => { body: null, valid_status_codes: ['200-299'], email: 'ksjaay@lunalytics.xyz', + notificationId: 'test', + notificationType: 'All', type: 'http', port: null, uptimePercentage: 100, averageHeartbeatLatency: 820, - lastCheck: 1715559831877, - nextCheck: 1715646231877, cert: { isValid: true, issuer: { C: 'US', O: "Let's Encrypt", CN: 'R3' }, @@ -88,7 +89,6 @@ describe('Monitor - Class', () => { validTill: 'Jul 14 01:56:21 2024 GMT', validOn: ['*.vercel.app', 'vercel.app'], daysRemaining: 62, - lastCheck: 1715559831877, nextCheck: 1715646231877, }, heartbeats: [], diff --git a/test/server/database/certificate.test.js b/test/server/database/certificate.test.js index 25cfe31..6d390e1 100644 --- a/test/server/database/certificate.test.js +++ b/test/server/database/certificate.test.js @@ -5,6 +5,7 @@ import { deleteCertificate, } from '../../../server/database/queries/certificate'; import SQLite from '../../../server/database/sqlite/setup'; +import cleanCertificate from '../../../server/class/certificate'; vi.mock('../../../server/database/sqlite/setup'); @@ -68,7 +69,7 @@ describe('Certificate - Database queries', () => { const cert = await fetchCertificate(monitorId); - expect(cert).toEqual(certificate); + expect(cert).toEqual(cleanCertificate(certificate)); }); }); diff --git a/test/server/middleware/monitor/add.test.js b/test/server/middleware/monitor/add.test.js index b2f7a35..1919285 100644 --- a/test/server/middleware/monitor/add.test.js +++ b/test/server/middleware/monitor/add.test.js @@ -4,9 +4,12 @@ import SQLite from '../../../../server/database/sqlite/setup'; import { userExists } from '../../../../server/database/queries/user'; import cache from '../../../../server/cache'; import monitorAdd from '../../../../server/middleware/monitor/add'; +import { createMonitor } from '../../../../server/database/queries/monitor'; -vi.mock('../../../../server/database/queries/user'); vi.mock('../../../../server/cache'); +vi.mock('../../../../server/database/queries/user'); +vi.mock('../../../../server/database/queries/monitor'); +// vi.mock('../../../../server/middleware/monitor/add'); describe('Add Monitor - Middleware', () => { const user = { @@ -22,30 +25,29 @@ describe('Add Monitor - Middleware', () => { beforeEach(() => { SQLiteBuilders = { - insert: vi.fn(), + insert: vi.fn().mockImplementation(() => { + return { returning: vi.fn().mockReturnValue([{ id: 1 }]) }; + }), where: vi.fn().mockImplementation(() => { - return { first: vi.fn().mockReturnValue(null) }; + return { + first: vi.fn().mockReturnValue(null), + select: vi.fn().mockImplementation(() => { + return { + orderBy: vi.fn().mockImplementation(() => { + return { limit: vi.fn() }; + }), + }; + }), + }; }), update: vi.fn(), }; SQLite.client = () => SQLiteBuilders; - cache = { - monitors: { - addOrEdit: vi.fn().mockImplementation(() => { - return { monitorId: 'test', interval: 60 }; - }), - }, - heartbeats: { - get: vi.fn().mockReturnValue([]), - }, - certificates: { - get: vi.fn().mockReturnValue({ isValid: false }), - }, - checkStatus: vi.fn(), - }; + cache = { checkStatus: vi.fn() }; userExists = vi.fn().mockReturnValue({ email: 'KSJaay@lunalytics.xyz' }); + createMonitor = vi.fn().mockReturnValue({ monitorId: 'test' }); fakeRequest = createRequest(); fakeResponse = createResponse(); @@ -65,7 +67,7 @@ describe('Add Monitor - Middleware', () => { url: 'https://lunalytics.xyz', interval: 60, email: 'KSJaay@lunalytics.xyz', - valid_status_codes: JSON.stringify(['200-299']), + valid_status_codes: ['200-299'], notificationType: 'All', notificationId: null, method: 'GET', @@ -142,14 +144,25 @@ describe('Add Monitor - Middleware', () => { expect(userExists).toHaveBeenCalledWith(access_token); }); - it('should call addOrEdit with body, email, and isHttp', async () => { + it('should call createMonitor with body, email, and isHttp', async () => { await monitorAdd(fakeRequest, fakeResponse); - expect(cache.monitors.addOrEdit).toHaveBeenCalledWith( - fakeRequest.body, - user.email, - true - ); + expect(createMonitor).toHaveBeenCalledWith({ + name: fakeRequest.body.name, + url: fakeRequest.body.url, + interval: fakeRequest.body.interval, + monitorId: fakeRequest.body.monitorId, + retryInterval: fakeRequest.body.retryInterval, + requestTimeout: fakeRequest.body.requestTimeout, + notificationId: fakeRequest.body.notificationId, + notificationType: fakeRequest.body.notificationType, + email: user.email, + method: fakeRequest.body.method, + valid_status_codes: JSON.stringify( + fakeRequest.body.valid_status_codes + ), + type: 'http', + }); }); it('should call setTimeout with monitorId and interval', async () => { @@ -246,11 +259,20 @@ describe('Add Monitor - Middleware', () => { it('should call addOrEdit with body, email, and isHttp', async () => { await monitorAdd(fakeRequest, fakeResponse); - expect(cache.monitors.addOrEdit).toHaveBeenCalledWith( - fakeRequest.body, - user.email, - false - ); + expect(createMonitor).toHaveBeenCalledWith({ + name: fakeRequest.body.name, + url: fakeRequest.body.url, + interval: fakeRequest.body.interval, + monitorId: fakeRequest.body.monitorId, + retryInterval: fakeRequest.body.retryInterval, + requestTimeout: fakeRequest.body.requestTimeout, + notificationId: fakeRequest.body.notificationId, + notificationType: fakeRequest.body.notificationType, + email: user.email, + port: fakeRequest.body.port, + valid_status_codes: '', + type: 'tcp', + }); }); it('should call setTimeout with monitorId and interval', async () => { diff --git a/test/server/middleware/monitor/delete.test.js b/test/server/middleware/monitor/delete.test.js index c83237f..a77d5c5 100644 --- a/test/server/middleware/monitor/delete.test.js +++ b/test/server/middleware/monitor/delete.test.js @@ -1,9 +1,13 @@ import { describe, expect, it, vi } from 'vitest'; import { createRequest, createResponse } from 'node-mocks-http'; -import cache from '../../../../server/cache'; import monitorDelete from '../../../../server/middleware/monitor/delete'; +import { deleteMonitor } from '../../../../server/database/queries/monitor'; +import { deleteHeartbeats } from '../../../../server/database/queries/heartbeat'; +import { deleteCertificate } from '../../../../server/database/queries/certificate'; -vi.mock('../../../../server/cache'); +vi.mock('../../../../server/database/queries/monitor'); +vi.mock('../../../../server/database/queries/heartbeat'); +vi.mock('../../../../server/database/queries/certificate'); describe('Delete Monitor - Middleware', () => { const monitorId = 'test_monitor_id'; @@ -12,12 +16,6 @@ describe('Delete Monitor - Middleware', () => { let fakeResponse; beforeEach(() => { - cache = { - monitors: { delete: vi.fn() }, - heartbeats: { delete: vi.fn() }, - certificates: { delete: vi.fn() }, - }; - fakeRequest = createRequest(); fakeResponse = createResponse(); @@ -41,22 +39,22 @@ describe('Delete Monitor - Middleware', () => { }); describe('when monitorId is valid', () => { - it('should call cache.monitors.delete with monitorId', async () => { + it('should call deleteMonitor with monitorId', async () => { await monitorDelete(fakeRequest, fakeResponse); - expect(cache.monitors.delete).toHaveBeenCalledWith(monitorId); + expect(deleteMonitor).toHaveBeenCalledWith(monitorId); }); - it('should call cache.heartbeats.delete with monitorId', async () => { + it('should call deleteHeartbeats with monitorId', async () => { await monitorDelete(fakeRequest, fakeResponse); - expect(cache.heartbeats.delete).toHaveBeenCalledWith(monitorId); + expect(deleteHeartbeats).toHaveBeenCalledWith(monitorId); }); - it('should call cache.certificates.delete with monitorId', async () => { + it('should call deleteCertificate with monitorId', async () => { await monitorDelete(fakeRequest, fakeResponse); - expect(cache.certificates.delete).toHaveBeenCalledWith(monitorId); + expect(deleteCertificate).toHaveBeenCalledWith(monitorId); }); }); }); diff --git a/test/server/middleware/monitor/edit.test.js b/test/server/middleware/monitor/edit.test.js index ed56560..1749e96 100644 --- a/test/server/middleware/monitor/edit.test.js +++ b/test/server/middleware/monitor/edit.test.js @@ -4,9 +4,11 @@ import SQLite from '../../../../server/database/sqlite/setup'; import { userExists } from '../../../../server/database/queries/user'; import cache from '../../../../server/cache'; import monitorEdit from '../../../../server/middleware/monitor/edit'; +import { updateMonitor } from '../../../../server/database/queries/monitor'; -vi.mock('../../../../server/database/queries/user'); vi.mock('../../../../server/cache'); +vi.mock('../../../../server/database/queries/user'); +vi.mock('../../../../server/database/queries/monitor'); describe('Edit Monitor - Middleware', () => { const user = { @@ -22,30 +24,29 @@ describe('Edit Monitor - Middleware', () => { beforeEach(() => { SQLiteBuilders = { - insert: vi.fn(), + insert: vi.fn().mockImplementation(() => { + return { returning: vi.fn().mockReturnValue([{ id: 1 }]) }; + }), where: vi.fn().mockImplementation(() => { - return { first: vi.fn().mockReturnValue(null) }; + return { + first: vi.fn().mockReturnValue(null), + select: vi.fn().mockImplementation(() => { + return { + orderBy: vi.fn().mockImplementation(() => { + return { limit: vi.fn() }; + }), + }; + }), + }; }), update: vi.fn(), }; SQLite.client = () => SQLiteBuilders; - cache = { - monitors: { - addOrEdit: vi.fn().mockImplementation(() => { - return { monitorId: 'test', interval: 60 }; - }), - }, - heartbeats: { - get: vi.fn().mockReturnValue([]), - }, - certificates: { - get: vi.fn().mockReturnValue({ isValid: false }), - }, - checkStatus: vi.fn(), - }; + cache = { checkStatus: vi.fn() }; userExists = vi.fn().mockReturnValue({ email: 'KSJaay@lunalytics.xyz' }); + updateMonitor = vi.fn().mockReturnValue({ monitorId: 'test' }); fakeRequest = createRequest(); fakeResponse = createResponse(); @@ -65,7 +66,7 @@ describe('Edit Monitor - Middleware', () => { url: 'https://lunalytics.xyz', interval: 60, email: 'KSJaay@lunalytics.xyz', - valid_status_codes: JSON.stringify(['200-299']), + valid_status_codes: ['200-299'], notificationType: 'All', notificationId: null, method: 'GET', @@ -142,15 +143,25 @@ describe('Edit Monitor - Middleware', () => { expect(userExists).toHaveBeenCalledWith(access_token); }); - it('should call addOrEdit with body, email, and isHttp', async () => { + it('should call updateMonitor with body, email, and isHttp', async () => { await monitorEdit(fakeRequest, fakeResponse); - expect(cache.monitors.addOrEdit).toHaveBeenCalledWith( - fakeRequest.body, - user.email, - true, - true - ); + expect(updateMonitor).toHaveBeenCalledWith({ + name: fakeRequest.body.name, + url: fakeRequest.body.url, + interval: fakeRequest.body.interval, + monitorId: fakeRequest.body.monitorId, + retryInterval: fakeRequest.body.retryInterval, + requestTimeout: fakeRequest.body.requestTimeout, + notificationId: fakeRequest.body.notificationId, + notificationType: fakeRequest.body.notificationType, + email: user.email, + method: fakeRequest.body.method, + valid_status_codes: JSON.stringify( + fakeRequest.body.valid_status_codes + ), + type: 'http', + }); }); it('should call checkStatus with monitorId and interval', async () => { @@ -247,12 +258,20 @@ describe('Edit Monitor - Middleware', () => { it('should call addOrEdit with body, email, and isHttp', async () => { await monitorEdit(fakeRequest, fakeResponse); - expect(cache.monitors.addOrEdit).toHaveBeenCalledWith( - fakeRequest.body, - user.email, - false, - true - ); + expect(updateMonitor).toHaveBeenCalledWith({ + name: fakeRequest.body.name, + url: fakeRequest.body.url, + interval: fakeRequest.body.interval, + monitorId: fakeRequest.body.monitorId, + retryInterval: fakeRequest.body.retryInterval, + requestTimeout: fakeRequest.body.requestTimeout, + notificationId: fakeRequest.body.notificationId, + notificationType: fakeRequest.body.notificationType, + email: user.email, + port: fakeRequest.body.port, + valid_status_codes: '', + type: 'tcp', + }); }); it('should call checkStatus with monitorId and interval', async () => { diff --git a/test/server/middleware/monitor/id.test.js b/test/server/middleware/monitor/id.test.js index 904b0ff..cbfda64 100644 --- a/test/server/middleware/monitor/id.test.js +++ b/test/server/middleware/monitor/id.test.js @@ -1,9 +1,13 @@ import { describe, expect, it, vi } from 'vitest'; import { createRequest, createResponse } from 'node-mocks-http'; -import cache from '../../../../server/cache'; import fetchMonitorUsingId from '../../../../server/middleware/monitor/id'; +import { fetchCertificate } from '../../../../server/database/queries/certificate'; +import { fetchMonitor } from '../../../../server/database/queries/monitor'; +import { fetchHeartbeats } from '../../../../server/database/queries/heartbeat'; -vi.mock('../../../../server/cache'); +vi.mock('../../../../server/database/queries/certificate'); +vi.mock('../../../../server/database/queries/monitor'); +vi.mock('../../../../server/database/queries/heartbeat'); describe('Fetch Monitor Using Id - Middleware', () => { const monitorId = 'test_monitor_id'; @@ -12,15 +16,13 @@ describe('Fetch Monitor Using Id - Middleware', () => { let fakeResponse; beforeEach(() => { - cache = { - monitors: { get: vi.fn().mockReturnValue({ monitorId }) }, - heartbeats: { get: vi.fn().mockReturnValue({ isValid: false }) }, - certificates: { get: vi.fn().mockReturnValue([]) }, - }; - fakeRequest = createRequest(); fakeResponse = createResponse(); + fetchCertificate = vi.fn().mockReturnValue({ isValid: false }); + fetchMonitor = vi.fn().mockReturnValue({ monitorId: 'test_monitor_id' }); + fetchHeartbeats = vi.fn().mockReturnValue([]); + fakeRequest.query = { monitorId, }; @@ -41,30 +43,30 @@ describe('Fetch Monitor Using Id - Middleware', () => { }); describe('when monitorId is valid', () => { - it('should call cache.monitors.get with monitorId', async () => { + it('should call fetchMonitor with monitorId', async () => { await fetchMonitorUsingId(fakeRequest, fakeResponse); - expect(cache.monitors.get).toHaveBeenCalledWith(monitorId); + expect(fetchMonitor).toHaveBeenCalledWith(monitorId); }); it('should return 404 when monitor is not found', async () => { - cache.monitors.get.mockReturnValue(null); + fetchMonitor.mockReturnValue(null); await fetchMonitorUsingId(fakeRequest, fakeResponse); expect(fakeResponse.statusCode).toEqual(404); }); - it('should call cache.heartbeats.get with monitorId', async () => { + it('should call fetchHeartbeats with monitorId', async () => { await fetchMonitorUsingId(fakeRequest, fakeResponse); - expect(cache.heartbeats.get).toHaveBeenCalledWith(monitorId); + expect(fetchHeartbeats).toHaveBeenCalledWith(monitorId); }); - it('should call cache.certificates.get with monitorId', async () => { + it('should call fetchCertificate with monitorId', async () => { await fetchMonitorUsingId(fakeRequest, fakeResponse); - expect(cache.certificates.get).toHaveBeenCalledWith(monitorId); + expect(fetchCertificate).toHaveBeenCalledWith(monitorId); }); it('should return 200 when data is valid', async () => { diff --git a/test/server/middleware/monitor/status.test.js b/test/server/middleware/monitor/status.test.js index d699664..2c191fb 100644 --- a/test/server/middleware/monitor/status.test.js +++ b/test/server/middleware/monitor/status.test.js @@ -1,9 +1,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createRequest, createResponse } from 'node-mocks-http'; -import cache from '../../../../server/cache'; import fetchMonitorStatus from '../../../../server/middleware/monitor/status'; +import { fetchMonitor } from '../../../../server/database/queries/monitor'; +import { + fetchDailyHeartbeats, + fetchHeartbeats, + fetchHourlyHeartbeats, +} from '../../../../server/database/queries/heartbeat'; -vi.mock('../../../../server/cache'); +vi.mock('../../../../server/database/queries/monitor'); +vi.mock('../../../../server/database/queries/heartbeat'); describe('Fetch Monitor Status - Middleware', () => { const monitorId = 'test_monitor_id'; @@ -18,16 +24,11 @@ describe('Fetch Monitor Status - Middleware', () => { }; beforeEach(() => { - cache = { - monitors: { get: vi.fn().mockReturnValue({ monitorId }) }, - heartbeats: { - get: vi.fn().mockReturnValue(true), - getRealtime: vi.fn().mockReturnValue([heartbeat, heartbeat]), - getDaily: vi.fn().mockReturnValue([heartbeat, heartbeat]), - getWeekly: vi.fn().mockReturnValue([heartbeat, heartbeat]), - getMonthly: vi.fn().mockReturnValue([heartbeat, heartbeat]), - }, - }; + fetchMonitor = vi.fn().mockReturnValue({ monitorId }); + fetchHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); + fetchHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); + fetchHourlyHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); + fetchDailyHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); fakeRequest = createRequest(); fakeResponse = createResponse(); @@ -63,16 +64,16 @@ describe('Fetch Monitor Status - Middleware', () => { }); describe('when monitorId is valid', () => { - it('should call cache.monitors.get with monitorId', async () => { + it('should call fetchMonitor with monitorId', async () => { await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(cache.monitors.get).toHaveBeenCalledWith(monitorId); + expect(fetchMonitor).toHaveBeenCalledWith(monitorId); }); - it('should call cache.heartbeats.getRealtime with monitorId', async () => { + it('should call fetchHeartbeats with monitorId', async () => { await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(cache.heartbeats.getRealtime).toHaveBeenCalledWith(monitorId); + expect(fetchHeartbeats).toHaveBeenCalledWith(monitorId); }); it('should return 200 when data is valid', async () => { @@ -87,49 +88,106 @@ describe('Fetch Monitor Status - Middleware', () => { expect(fakeResponse.json).toHaveBeenCalledWith([heartbeat, heartbeat]); }); - [ - { type: 'day', funcName: 'getDaily' }, - { type: 'week', funcName: 'getWeekly' }, - { type: 'month', funcName: 'getMonthly' }, - ].forEach(({ type, funcName }) => - describe(`when type is ${type}`, () => { - beforeEach(() => { - fakeRequest.query.type = type; - }); + describe(`when type is day`, () => { + beforeEach(() => { + fakeRequest.query.type = 'day'; + }); + + it(`should call fetchDailyHeartbeats with monitorId`, async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fetchDailyHeartbeats).toHaveBeenCalledWith(monitorId); + }); + + it('should return 416 when heartbeats are less than two', async () => { + fetchDailyHeartbeats = vi.fn().mockReturnValue([]); + + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.statusCode).toEqual(416); + }); + + it('should return 200 when data is valid', async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.statusCode).toEqual(200); + }); + + it('should call response.json with heartbeats', async () => { + fetchDailyHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); + + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.json).toHaveBeenCalledWith([heartbeat, heartbeat]); + }); + }); + + describe(`when type is week`, () => { + beforeEach(() => { + fakeRequest.query.type = 'week'; + }); - it(`should call cache.heartbeats.${funcName} with monitorId`, async () => { - await fetchMonitorStatus(fakeRequest, fakeResponse); + it(`should call fetchHourlyHeartbeats with monitorId`, async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(cache.heartbeats[funcName]).toHaveBeenCalledWith(monitorId); - }); + expect(fetchHourlyHeartbeats).toHaveBeenCalledWith(monitorId, 168); + }); - it('should return 416 when heartbeats are less than two', async () => { - cache.heartbeats[funcName] = vi.fn().mockReturnValue([]); + it('should return 416 when heartbeats are less than two', async () => { + fetchHourlyHeartbeats = vi.fn().mockReturnValue([]); - await fetchMonitorStatus(fakeRequest, fakeResponse); + await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(fakeResponse.statusCode).toEqual(416); - }); + expect(fakeResponse.statusCode).toEqual(416); + }); - it('should return 200 when data is valid', async () => { - await fetchMonitorStatus(fakeRequest, fakeResponse); + it('should return 200 when data is valid', async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(fakeResponse.statusCode).toEqual(200); - }); + expect(fakeResponse.statusCode).toEqual(200); + }); - it('should call response.json with heartbeats', async () => { - cache.heartbeats[funcName] = vi - .fn() - .mockReturnValue([heartbeat, heartbeat]); + it('should call response.json with heartbeats', async () => { + fetchHourlyHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); - await fetchMonitorStatus(fakeRequest, fakeResponse); + await fetchMonitorStatus(fakeRequest, fakeResponse); - expect(fakeResponse.json).toHaveBeenCalledWith([ - heartbeat, - heartbeat, - ]); - }); - }) - ); + expect(fakeResponse.json).toHaveBeenCalledWith([heartbeat, heartbeat]); + }); + }); + + describe(`when type is month`, () => { + beforeEach(() => { + fakeRequest.query.type = 'month'; + }); + + it(`should call fetchHourlyHeartbeats with monitorId`, async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fetchHourlyHeartbeats).toHaveBeenCalledWith(monitorId, 720); + }); + + it('should return 416 when heartbeats are less than two', async () => { + fetchHourlyHeartbeats = vi.fn().mockReturnValue([]); + + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.statusCode).toEqual(416); + }); + + it('should return 200 when data is valid', async () => { + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.statusCode).toEqual(200); + }); + + it('should call response.json with heartbeats', async () => { + fetchHourlyHeartbeats = vi.fn().mockReturnValue([heartbeat, heartbeat]); + + await fetchMonitorStatus(fakeRequest, fakeResponse); + + expect(fakeResponse.json).toHaveBeenCalledWith([heartbeat, heartbeat]); + }); + }); }); }); diff --git a/vite.config.js b/vite.config.js index 1de0d12..be54c38 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,15 +1,15 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react-swc'; import { visualizer } from 'rollup-plugin-visualizer'; -import viteCompression from 'vite-plugin-compression2'; +import { compression } from 'vite-plugin-compression2'; const filter = /\.(js|mjs|json|css|svg|html)$/i; export default defineConfig({ plugins: [ react(), - viteCompression({ algorithm: 'gzip', filter }), - viteCompression({ algorithm: 'brotliCompress', filter }), + compression({ algorithm: 'gzip', filter }), + compression({ algorithm: 'brotliCompress', filter }), visualizer({ filename: './stats/stats.html' }), ], define: { __APP_VERSION__: JSON.stringify(process.env.npm_package_version) },