diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 6d3474ff..c57a6542 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -7511,6 +7511,17 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", diff --git a/node_modules/node-cron/.circleci/config.yml b/node_modules/node-cron/.circleci/config.yml new file mode 100644 index 00000000..bb5999a2 --- /dev/null +++ b/node_modules/node-cron/.circleci/config.yml @@ -0,0 +1,17 @@ +version: 2.1 + +jobs: + test: + docker: + - image: cimg/node:17.4 + environment: + TZ: "America/Sao_Paulo" + steps: + - checkout + - run: npm install + - run: COVERALLS_GIT_BRANCH=$CIRCLE_BRANCH npm run check + +workflows: + build: + jobs: + - test diff --git a/node_modules/node-cron/.covignore b/node_modules/node-cron/.covignore new file mode 100644 index 00000000..2ccbe465 --- /dev/null +++ b/node_modules/node-cron/.covignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/node_modules/node-cron/.eslintrc.yml b/node_modules/node-cron/.eslintrc.yml new file mode 100644 index 00000000..d83b2587 --- /dev/null +++ b/node_modules/node-cron/.eslintrc.yml @@ -0,0 +1,20 @@ +env: + es6: true + node: true + mocha: true +extends: 'eslint:recommended' +parserOptions: + ecmaVersion: 2018 +rules: + indent: + - error + - 4 + linebreak-style: + - error + - unix + quotes: + - error + - single + semi: + - error + - always diff --git a/node_modules/node-cron/.github/stale.yml b/node_modules/node-cron/.github/stale.yml new file mode 100644 index 00000000..d9f65632 --- /dev/null +++ b/node_modules/node-cron/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false \ No newline at end of file diff --git a/node_modules/node-cron/LICENSE.md b/node_modules/node-cron/LICENSE.md new file mode 100644 index 00000000..3da3ff00 --- /dev/null +++ b/node_modules/node-cron/LICENSE.md @@ -0,0 +1,7 @@ +## ISC License + +Copyright (c) 2016, Lucas Merencia \ + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/node-cron/README.md b/node_modules/node-cron/README.md new file mode 100644 index 00000000..73e8f7ba --- /dev/null +++ b/node_modules/node-cron/README.md @@ -0,0 +1,247 @@ +# Node Cron + +[![npm](https://img.shields.io/npm/l/node-cron.svg)](https://github.com/merencia/node-cron/blob/master/LICENSE.md) +[![npm](https://img.shields.io/npm/v/node-cron.svg)](https://img.shields.io/npm/v/node-cron.svg) +[![Coverage Status](https://coveralls.io/repos/github/node-cron/node-cron/badge.svg?branch=master)](https://coveralls.io/github/node-cron/node-cron?branch=master) +[![Code Climate](https://codeclimate.com/github/node-cron/node-cron/badges/gpa.svg)](https://codeclimate.com/github/merencia/node-cron) +[![Build Status](https://travis-ci.org/node-cron/node-cron.svg?branch=master)](https://travis-ci.org/merencia/node-cron) +[![Dependency Status](https://david-dm.org/node-cron/node-cron.svg)](https://david-dm.org/merencia/node-cron) +[![devDependency Status](https://david-dm.org/node-cron/node-cron/dev-status.svg)](https://david-dm.org/merencia/node-cron#info=devDependencies) +[![Backers on Open Collective](https://opencollective.com/node-cron/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/node-cron/sponsors/badge.svg)](#sponsors) + +The node-cron module is tiny task scheduler in pure JavaScript for node.js based on [GNU crontab](https://www.gnu.org/software/mcron/manual/html_node/Crontab-file.html). This module allows you to schedule task in node.js using full crontab syntax. + +**Need a job scheduler with support for worker threads and cron syntax?** Try out the [Bree](https://github.com/breejs/bree) job scheduler! + +[![NPM](https://nodei.co/npm/node-cron.png?downloads=true&downloadRank=true&stars=false)](https://nodei.co/npm/node-cron/) + + +## Getting Started + +Install node-cron using npm: + +```console +npm install --save node-cron +``` + +Import node-cron and schedule a task: + +```javascript +var cron = require('node-cron'); + +cron.schedule('* * * * *', () => { + console.log('running a task every minute'); +}); +``` + +## Cron Syntax + +This is a quick reference to cron syntax and also shows the options supported by node-cron. + +### Allowed fields + +``` + # ┌────────────── second (optional) + # │ ┌──────────── minute + # │ │ ┌────────── hour + # │ │ │ ┌──────── day of month + # │ │ │ │ ┌────── month + # │ │ │ │ │ ┌──── day of week + # │ │ │ │ │ │ + # │ │ │ │ │ │ + # * * * * * * +``` + +### Allowed values + +| field | value | +|--------------|---------------------| +| second | 0-59 | +| minute | 0-59 | +| hour | 0-23 | +| day of month | 1-31 | +| month | 1-12 (or names) | +| day of week | 0-7 (or names, 0 or 7 are sunday) | + + +#### Using multiples values + +You may use multiples values separated by comma: + +```javascript +var cron = require('node-cron'); + +cron.schedule('1,2,4,5 * * * *', () => { + console.log('running every minute 1, 2, 4 and 5'); +}); +``` + +#### Using ranges + +You may also define a range of values: + +```javascript +var cron = require('node-cron'); + +cron.schedule('1-5 * * * *', () => { + console.log('running every minute to 1 from 5'); +}); +``` + +#### Using step values + +Step values can be used in conjunction with ranges, following a range with '/' and a number. e.g: `1-10/2` that is the same as `2,4,6,8,10`. Steps are also permitted after an asterisk, so if you want to say “every two minutes”, just use `*/2`. + +```javascript +var cron = require('node-cron'); + +cron.schedule('*/2 * * * *', () => { + console.log('running a task every two minutes'); +}); +``` + +#### Using names + +For month and week day you also may use names or short names. e.g: + +```javascript +var cron = require('node-cron'); + +cron.schedule('* * * January,September Sunday', () => { + console.log('running on Sundays of January and September'); +}); +``` + +Or with short names: + +```javascript +var cron = require('node-cron'); + +cron.schedule('* * * Jan,Sep Sun', () => { + console.log('running on Sundays of January and September'); +}); +``` + +## Cron methods + +### Schedule + +Schedules given task to be executed whenever the cron expression ticks. + +Arguments: + +- **expression** `string`: Cron expression +- **function** `Function`: Task to be executed +- **options** `Object`: Optional configuration for job scheduling. + +#### Options + + - **scheduled**: A `boolean` to set if the created task is scheduled. Default `true`; + - **timezone**: The timezone that is used for job scheduling. See [IANA time zone database](https://www.iana.org/time-zones) for valid values, such as `Asia/Shanghai`, `Asia/Kolkata`, `America/Sao_Paulo`. + + **Example**: + + ```js + var cron = require('node-cron'); + + cron.schedule('0 1 * * *', () => { + console.log('Running a job at 01:00 at America/Sao_Paulo timezone'); + }, { + scheduled: true, + timezone: "America/Sao_Paulo" + }); + ``` + +## ScheduledTask methods + +### Start + +Starts the scheduled task. + +```javascript +var cron = require('node-cron'); + +var task = cron.schedule('* * * * *', () => { + console.log('stopped task'); +}, { + scheduled: false +}); + +task.start(); +``` + +### Stop + +The task won't be executed unless re-started. + +```javascript +var cron = require('node-cron'); + +var task = cron.schedule('* * * * *', () => { + console.log('will execute every minute until stopped'); +}); + +task.stop(); +``` + +### Validate + +Validate that the given string is a valid cron expression. + +```javascript +var cron = require('node-cron'); + +var valid = cron.validate('59 * * * *'); +var invalid = cron.validate('60 * * * *'); +``` + +## Issues + +Feel free to submit issues and enhancement requests [here](https://github.com/merencia/node-cron/issues). + +## Contributing + +In general, we follow the "fork-and-pull" Git workflow. + + - Fork the repo on GitHub; + - Commit changes to a branch in your fork; + - Pull request "upstream" with your changes; + +NOTE: Be sure to merge the latest from "upstream" before making a pull request! + +Please do not contribute code you did not write yourself, unless you are certain you have the legal ability to do so. Also ensure all contributed code can be distributed under the ISC License. + +## Contributors + +This project exists thanks to all the people who contribute. + + + +## Backers + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/node-cron#backer)] + + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/node-cron#sponsor)] + + + + + + + + + + + + + + +## License + +node-cron is under [ISC License](https://github.com/merencia/node-cron/blob/master/LICENSE.md). diff --git a/node_modules/node-cron/package.json b/node_modules/node-cron/package.json new file mode 100644 index 00000000..da0feb28 --- /dev/null +++ b/node_modules/node-cron/package.json @@ -0,0 +1,43 @@ +{ + "name": "node-cron", + "version": "3.0.2", + "description": "A simple cron-like task scheduler for Node.js", + "author": "Lucas Merencia", + "license": "ISC", + "homepage": "https://github.com/merencia/node-cron", + "main": "src/node-cron.js", + "scripts": { + "test": "nyc --reporter=html --reporter=text mocha --recursive", + "lint": "./node_modules/.bin/eslint ./src ./test", + "check": "npm run lint && npm test" + }, + "engines": { + "node": ">=6.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/merencia/node-cron.git" + }, + "keywords": [ + "cron", + "scheduler", + "schedule", + "task", + "job" + ], + "bugs": { + "url": "https://github.com/merencia/node-cron/issues" + }, + "dependencies": { + "uuid": "8.3.2" + }, + "devDependencies": { + "chai": "^4.2.0", + "eslint": "^5.7.0", + "istanbul": "^0.4.2", + "mocha": "^6.1.4", + "moment-timezone": "^0.5.33", + "nyc": "^14.0.0", + "sinon": "^7.3.2" + } +} diff --git a/node_modules/node-cron/src/background-scheduled-task/daemon.js b/node_modules/node-cron/src/background-scheduled-task/daemon.js new file mode 100644 index 00000000..022eb692 --- /dev/null +++ b/node_modules/node-cron/src/background-scheduled-task/daemon.js @@ -0,0 +1,19 @@ +const ScheduledTask = require('../scheduled-task'); + +let scheduledTask; + +function register(message){ + const script = require(message.path); + scheduledTask = new ScheduledTask(message.cron, script.task, message.options); + scheduledTask.on('task-done', (result) => { + process.send({ type: 'task-done', result}); + }); + process.send({ type: 'registred' }); +} + +process.on('message', (message) => { + switch(message.type){ + case 'register': + return register(message); + } +}); diff --git a/node_modules/node-cron/src/background-scheduled-task/index.js b/node_modules/node-cron/src/background-scheduled-task/index.js new file mode 100644 index 00000000..5e92e2e0 --- /dev/null +++ b/node_modules/node-cron/src/background-scheduled-task/index.js @@ -0,0 +1,67 @@ +const EventEmitter = require('events'); +const path = require('path'); +const { fork } = require('child_process'); +const uuid = require('uuid'); + +const daemonPath = `${__dirname}/daemon.js`; + +class BackgroundScheduledTask extends EventEmitter { + constructor(cronExpression, taskPath, options){ + super(); + if(!options){ + options = { + scheduled: true, + recoverMissedExecutions: false, + }; + } + this.cronExpression = cronExpression; + this.taskPath = taskPath; + this.options = options; + this.options.name = this.options.name || uuid.v4(); + + if(options.scheduled){ + this.start(); + } + } + + start() { + this.stop(); + this.forkProcess = fork(daemonPath); + + this.forkProcess.on('message', (message) => { + switch(message.type){ + case 'task-done': + this.emit('task-done', message.result); + break; + } + }); + + let options = this.options; + options.scheduled = true; + + this.forkProcess.send({ + type: 'register', + path: path.resolve(this.taskPath), + cron: this.cronExpression, + options: options + }); + } + + stop(){ + if(this.forkProcess){ + this.forkProcess.kill(); + } + } + + pid() { + if(this.forkProcess){ + return this.forkProcess.pid; + } + } + + isRunning(){ + return !this.forkProcess.killed; + } +} + +module.exports = BackgroundScheduledTask; \ No newline at end of file diff --git a/node_modules/node-cron/src/convert-expression/asterisk-to-range-conversion.js b/node_modules/node-cron/src/convert-expression/asterisk-to-range-conversion.js new file mode 100644 index 00000000..327346ee --- /dev/null +++ b/node_modules/node-cron/src/convert-expression/asterisk-to-range-conversion.js @@ -0,0 +1,21 @@ +'use strict'; +module.exports = (() => { + function convertAsterisk(expression, replecement){ + if(expression.indexOf('*') !== -1){ + return expression.replace('*', replecement); + } + return expression; + } + + function convertAsterisksToRanges(expressions){ + expressions[0] = convertAsterisk(expressions[0], '0-59'); + expressions[1] = convertAsterisk(expressions[1], '0-59'); + expressions[2] = convertAsterisk(expressions[2], '0-23'); + expressions[3] = convertAsterisk(expressions[3], '1-31'); + expressions[4] = convertAsterisk(expressions[4], '1-12'); + expressions[5] = convertAsterisk(expressions[5], '0-6'); + return expressions; + } + + return convertAsterisksToRanges; +})(); diff --git a/node_modules/node-cron/src/convert-expression/index.js b/node_modules/node-cron/src/convert-expression/index.js new file mode 100644 index 00000000..c1a992d4 --- /dev/null +++ b/node_modules/node-cron/src/convert-expression/index.js @@ -0,0 +1,66 @@ +'use strict'; + +const monthNamesConversion = require('./month-names-conversion'); +const weekDayNamesConversion = require('./week-day-names-conversion'); +const convertAsterisksToRanges = require('./asterisk-to-range-conversion'); +const convertRanges = require('./range-conversion'); +const convertSteps = require('./step-values-conversion'); + +module.exports = (() => { + + function appendSeccondExpression(expressions){ + if(expressions.length === 5){ + return ['0'].concat(expressions); + } + return expressions; + } + + function removeSpaces(str) { + return str.replace(/\s{2,}/g, ' ').trim(); + } + + // Function that takes care of normalization. + function normalizeIntegers(expressions) { + for (let i=0; i < expressions.length; i++){ + const numbers = expressions[i].split(','); + for (let j=0; j { + const months = ['january','february','march','april','may','june','july', + 'august','september','october','november','december']; + const shortMonths = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', + 'sep', 'oct', 'nov', 'dec']; + + function convertMonthName(expression, items){ + for(let i = 0; i < items.length; i++){ + expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10) + 1); + } + return expression; + } + + function interprete(monthExpression){ + monthExpression = convertMonthName(monthExpression, months); + monthExpression = convertMonthName(monthExpression, shortMonths); + return monthExpression; + } + + return interprete; +})(); diff --git a/node_modules/node-cron/src/convert-expression/range-conversion.js b/node_modules/node-cron/src/convert-expression/range-conversion.js new file mode 100644 index 00000000..9b69017d --- /dev/null +++ b/node_modules/node-cron/src/convert-expression/range-conversion.js @@ -0,0 +1,39 @@ +'use strict'; +module.exports = ( () => { + function replaceWithRange(expression, text, init, end) { + + const numbers = []; + let last = parseInt(end); + let first = parseInt(init); + + if(first > last){ + last = parseInt(init); + first = parseInt(end); + } + + for(let i = first; i <= last; i++) { + numbers.push(i); + } + + return expression.replace(new RegExp(text, 'i'), numbers.join()); + } + + function convertRange(expression){ + const rangeRegEx = /(\d+)-(\d+)/; + let match = rangeRegEx.exec(expression); + while(match !== null && match.length > 0){ + expression = replaceWithRange(expression, match[0], match[1], match[2]); + match = rangeRegEx.exec(expression); + } + return expression; + } + + function convertAllRanges(expressions){ + for(let i = 0; i < expressions.length; i++){ + expressions[i] = convertRange(expressions[i]); + } + return expressions; + } + + return convertAllRanges; +})(); diff --git a/node_modules/node-cron/src/convert-expression/step-values-conversion.js b/node_modules/node-cron/src/convert-expression/step-values-conversion.js new file mode 100644 index 00000000..01b13ab3 --- /dev/null +++ b/node_modules/node-cron/src/convert-expression/step-values-conversion.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports = (() => { + function convertSteps(expressions){ + var stepValuePattern = /^(.+)\/(\w+)$/; + for(var i = 0; i < expressions.length; i++){ + var match = stepValuePattern.exec(expressions[i]); + var isStepValue = match !== null && match.length > 0; + if(isStepValue){ + var baseDivider = match[2]; + if(isNaN(baseDivider)){ + throw baseDivider + ' is not a valid step value'; + } + var values = match[1].split(','); + var stepValues = []; + var divider = parseInt(baseDivider, 10); + for(var j = 0; j <= values.length; j++){ + var value = parseInt(values[j], 10); + if(value % divider === 0){ + stepValues.push(value); + } + } + expressions[i] = stepValues.join(','); + } + } + return expressions; + } + + return convertSteps; +})(); diff --git a/node_modules/node-cron/src/convert-expression/week-day-names-conversion.js b/node_modules/node-cron/src/convert-expression/week-day-names-conversion.js new file mode 100644 index 00000000..03ef8b9a --- /dev/null +++ b/node_modules/node-cron/src/convert-expression/week-day-names-conversion.js @@ -0,0 +1,21 @@ +'use strict'; +module.exports = (() => { + const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', + 'friday', 'saturday']; + const shortWeekDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + + function convertWeekDayName(expression, items){ + for(let i = 0; i < items.length; i++){ + expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10)); + } + return expression; + } + + function convertWeekDays(expression){ + expression = expression.replace('7', '0'); + expression = convertWeekDayName(expression, weekDays); + return convertWeekDayName(expression, shortWeekDays); + } + + return convertWeekDays; +})(); diff --git a/node_modules/node-cron/src/node-cron.js b/node_modules/node-cron/src/node-cron.js new file mode 100644 index 00000000..459f161a --- /dev/null +++ b/node_modules/node-cron/src/node-cron.js @@ -0,0 +1,64 @@ +'use strict'; + +const ScheduledTask = require('./scheduled-task'); +const BackgroundScheduledTask = require('./background-scheduled-task'); +const validation = require('./pattern-validation'); +const storage = require('./storage'); + +/** + * @typedef {Object} CronScheduleOptions + * @prop {boolean} [scheduled] if a scheduled task is ready and running to be + * performed when the time matches the cron expression. + * @prop {string} [timezone] the timezone to execute the task in. + */ + +/** + * Creates a new task to execute the given function when the cron + * expression ticks. + * + * @param {string} expression The cron expression. + * @param {Function} func The task to be executed. + * @param {CronScheduleOptions} [options] A set of options for the scheduled task. + * @returns {ScheduledTask} The scheduled task. + */ +function schedule(expression, func, options) { + const task = createTask(expression, func, options); + + storage.save(task); + + return task; +} + +function createTask(expression, func, options) { + if (typeof func === 'string') + return new BackgroundScheduledTask(expression, func, options); + + return new ScheduledTask(expression, func, options); +} + +/** + * Check if a cron expression is valid. + * + * @param {string} expression The cron expression. + * @returns {boolean} Whether the expression is valid or not. + */ +function validate(expression) { + try { + validation(expression); + + return true; + } catch (_) { + return false; + } +} + +/** + * Gets the scheduled tasks. + * + * @returns {ScheduledTask[]} The scheduled tasks. + */ +function getTasks() { + return storage.getTasks(); +} + +module.exports = { schedule, validate, getTasks }; diff --git a/node_modules/node-cron/src/pattern-validation.js b/node_modules/node-cron/src/pattern-validation.js new file mode 100644 index 00000000..5bfca831 --- /dev/null +++ b/node_modules/node-cron/src/pattern-validation.js @@ -0,0 +1,124 @@ +'use strict'; + +const convertExpression = require('./convert-expression'); + +const validationRegex = /^(?:\d+|\*|\*\/\d+)$/; + +/** + * @param {string} expression The Cron-Job expression. + * @param {number} min The minimum value. + * @param {number} max The maximum value. + * @returns {boolean} + */ +function isValidExpression(expression, min, max) { + const options = expression.split(','); + + for (const option of options) { + const optionAsInt = parseInt(option, 10); + + if ( + (!Number.isNaN(optionAsInt) && + (optionAsInt < min || optionAsInt > max)) || + !validationRegex.test(option) + ) + return false; + } + + return true; +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidSecond(expression) { + return !isValidExpression(expression, 0, 59); +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidMinute(expression) { + return !isValidExpression(expression, 0, 59); +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidHour(expression) { + return !isValidExpression(expression, 0, 23); +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidDayOfMonth(expression) { + return !isValidExpression(expression, 1, 31); +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidMonth(expression) { + return !isValidExpression(expression, 1, 12); +} + +/** + * @param {string} expression The Cron-Job expression. + * @returns {boolean} + */ +function isInvalidWeekDay(expression) { + return !isValidExpression(expression, 0, 7); +} + +/** + * @param {string[]} patterns The Cron-Job expression patterns. + * @param {string[]} executablePatterns The executable Cron-Job expression + * patterns. + * @returns {void} + */ +function validateFields(patterns, executablePatterns) { + if (isInvalidSecond(executablePatterns[0])) + throw new Error(`${patterns[0]} is a invalid expression for second`); + + if (isInvalidMinute(executablePatterns[1])) + throw new Error(`${patterns[1]} is a invalid expression for minute`); + + if (isInvalidHour(executablePatterns[2])) + throw new Error(`${patterns[2]} is a invalid expression for hour`); + + if (isInvalidDayOfMonth(executablePatterns[3])) + throw new Error( + `${patterns[3]} is a invalid expression for day of month` + ); + + if (isInvalidMonth(executablePatterns[4])) + throw new Error(`${patterns[4]} is a invalid expression for month`); + + if (isInvalidWeekDay(executablePatterns[5])) + throw new Error(`${patterns[5]} is a invalid expression for week day`); +} + +/** + * Validates a Cron-Job expression pattern. + * + * @param {string} pattern The Cron-Job expression pattern. + * @returns {void} + */ +function validate(pattern) { + if (typeof pattern !== 'string') + throw new TypeError('pattern must be a string!'); + + const patterns = pattern.split(' '); + const executablePatterns = convertExpression(pattern).split(' '); + + if (patterns.length === 5) patterns.unshift('0'); + + validateFields(patterns, executablePatterns); +} + +module.exports = validate; diff --git a/node_modules/node-cron/src/scheduled-task.js b/node_modules/node-cron/src/scheduled-task.js new file mode 100644 index 00000000..52a1f590 --- /dev/null +++ b/node_modules/node-cron/src/scheduled-task.js @@ -0,0 +1,51 @@ +'use strict'; + +const EventEmitter = require('events'); +const Task = require('./task'); +const Scheduler = require('./scheduler'); +const uuid = require('uuid'); + +class ScheduledTask extends EventEmitter { + constructor(cronExpression, func, options) { + super(); + if(!options){ + options = { + scheduled: true, + recoverMissedExecutions: false + }; + } + + this.options = options; + this.options.name = this.options.name || uuid.v4(); + + this._task = new Task(func); + this._scheduler = new Scheduler(cronExpression, options.timezone, options.recoverMissedExecutions); + + this._scheduler.on('scheduled-time-matched', (now) => { + this.now(now); + }); + + if(options.scheduled !== false){ + this._scheduler.start(); + } + + if(options.runOnInit === true){ + this.now('init'); + } + } + + now(now = 'manual') { + let result = this._task.execute(now); + this.emit('task-done', result); + } + + start() { + this._scheduler.start(); + } + + stop() { + this._scheduler.stop(); + } +} + +module.exports = ScheduledTask; diff --git a/node_modules/node-cron/src/scheduler.js b/node_modules/node-cron/src/scheduler.js new file mode 100644 index 00000000..72febb4f --- /dev/null +++ b/node_modules/node-cron/src/scheduler.js @@ -0,0 +1,49 @@ +'use strict'; + +const EventEmitter = require('events'); +const TimeMatcher = require('./time-matcher'); + +class Scheduler extends EventEmitter{ + constructor(pattern, timezone, autorecover){ + super(); + this.timeMatcher = new TimeMatcher(pattern, timezone); + this.autorecover = autorecover; + } + + start(){ + // clear timeout if exists + this.stop(); + + let lastCheck = process.hrtime(); + let lastExecution = this.timeMatcher.apply(new Date()); + + const matchTime = () => { + const delay = 1000; + const elapsedTime = process.hrtime(lastCheck); + const elapsedMs = (elapsedTime[0] * 1e9 + elapsedTime[1]) / 1e6; + const missedExecutions = Math.floor(elapsedMs / 1000); + + for(let i = missedExecutions; i >= 0; i--){ + const date = new Date(new Date().getTime() - i * 1000); + let date_tmp = this.timeMatcher.apply(date); + if(lastExecution.getTime() < date_tmp.getTime() && (i === 0 || this.autorecover) && this.timeMatcher.match(date)){ + this.emit('scheduled-time-matched', date_tmp); + date_tmp.setMilliseconds(0); + lastExecution = date_tmp; + } + } + lastCheck = process.hrtime(); + this.timeout = setTimeout(matchTime, delay); + }; + matchTime(); + } + + stop(){ + if(this.timeout){ + clearTimeout(this.timeout); + } + this.timeout = null; + } +} + +module.exports = Scheduler; diff --git a/node_modules/node-cron/src/storage.js b/node_modules/node-cron/src/storage.js new file mode 100644 index 00000000..6c131a4f --- /dev/null +++ b/node_modules/node-cron/src/storage.js @@ -0,0 +1,19 @@ +module.exports = (() => { + if(!global.scheduledTasks){ + global.scheduledTasks = new Map(); + } + + return { + save: (task) => { + if(!task.options){ + const uuid = require('uuid'); + task.options = {}; + task.options.name = uuid.v4(); + } + global.scheduledTasks.set(task.options.name, task); + }, + getTasks: () => { + return global.scheduledTasks; + } + }; +})(); \ No newline at end of file diff --git a/node_modules/node-cron/src/task.js b/node_modules/node-cron/src/task.js new file mode 100644 index 00000000..66f49243 --- /dev/null +++ b/node_modules/node-cron/src/task.js @@ -0,0 +1,34 @@ +'use strict'; + +const EventEmitter = require('events'); + +class Task extends EventEmitter{ + constructor(execution){ + super(); + if(typeof execution !== 'function') { + throw 'execution must be a function'; + } + this._execution = execution; + } + + execute(now) { + let exec; + try { + exec = this._execution(now); + } catch (error) { + return this.emit('task-failed', error); + } + + if (exec instanceof Promise) { + return exec + .then(() => this.emit('task-finished')) + .catch((error) => this.emit('task-failed', error)); + } else { + this.emit('task-finished'); + return exec; + } + } +} + +module.exports = Task; + diff --git a/node_modules/node-cron/src/time-matcher.js b/node_modules/node-cron/src/time-matcher.js new file mode 100644 index 00000000..52a0b8ea --- /dev/null +++ b/node_modules/node-cron/src/time-matcher.js @@ -0,0 +1,54 @@ +const validatePattern = require('./pattern-validation'); +const convertExpression = require('./convert-expression'); + +function matchPattern(pattern, value){ + if( pattern.indexOf(',') !== -1 ){ + const patterns = pattern.split(','); + return patterns.indexOf(value.toString()) !== -1; + } + return pattern === value.toString(); +} + +class TimeMatcher{ + constructor(pattern, timezone){ + validatePattern(pattern); + this.pattern = convertExpression(pattern); + this.timezone = timezone; + this.expressions = this.pattern.split(' '); + } + + match(date){ + date = this.apply(date); + + const runOnSecond = matchPattern(this.expressions[0], date.getSeconds()); + const runOnMinute = matchPattern(this.expressions[1], date.getMinutes()); + const runOnHour = matchPattern(this.expressions[2], date.getHours()); + const runOnDay = matchPattern(this.expressions[3], date.getDate()); + const runOnMonth = matchPattern(this.expressions[4], date.getMonth() + 1); + const runOnWeekDay = matchPattern(this.expressions[5], date.getDay()); + + return runOnSecond && runOnMinute && runOnHour && runOnDay && runOnMonth && runOnWeekDay; + } + + apply(date){ + if(this.timezone){ + const dtf = new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hourCycle: 'h23', + fractionalSecondDigits: 3, + timeZone: this.timezone + }); + + return new Date(dtf.format(date)); + } + + return date; + } +} + +module.exports = TimeMatcher; \ No newline at end of file diff --git a/node_modules/node-cron/test/assets/dummy-task.js b/node_modules/node-cron/test/assets/dummy-task.js new file mode 100644 index 00000000..73b2c2a8 --- /dev/null +++ b/node_modules/node-cron/test/assets/dummy-task.js @@ -0,0 +1,3 @@ +exports.task = () => { + return 'dummy task'; +}; \ No newline at end of file diff --git a/node_modules/node-cron/test/background-scheduled-task-test.js b/node_modules/node-cron/test/background-scheduled-task-test.js new file mode 100644 index 00000000..0895ca9c --- /dev/null +++ b/node_modules/node-cron/test/background-scheduled-task-test.js @@ -0,0 +1,47 @@ +const { assert } = require('chai'); +const BackgroundScheduledTask = require('../src/background-scheduled-task'); + +describe('BackgroundScheduledTask', () => { + it('should start a task by default', (done) => { + let task = new BackgroundScheduledTask('* * * * * *', './test/assets/dummy-task.js'); + task.on('task-done', (result) => { + assert.equal('dummy task', result); + task.stop(); + done(); + }); + }); + it('should create a task stoped', () => { + let task = new BackgroundScheduledTask('* * * * * *', './test/assets/dummy-task.js', { + scheduled: false + }); + + assert.isUndefined(task.pid()); + }); + + it('should start a task', (done) => { + let task = new BackgroundScheduledTask('* * * * * *', './test/assets/dummy-task.js', { + scheduled: false + }); + + assert.isUndefined(task.pid()); + + task.on('task-done', (result) => { + assert.equal('dummy task', result); + task.stop(); + done(); + }); + + task.start(); + assert.isNotNull(task.pid()); + }); + + it('should stop a task', () => { + let task = new BackgroundScheduledTask('* * * * * *', './test/assets/dummy-task.js', { + scheduled: true + }); + assert.isNotNull(task.pid()); + assert.isTrue(task.isRunning()); + task.stop(); + assert.isFalse(task.isRunning()); + }); +}); \ No newline at end of file diff --git a/node_modules/node-cron/test/convert-expression/asterisk-to-range-conversion-test.js b/node_modules/node-cron/test/convert-expression/asterisk-to-range-conversion-test.js new file mode 100644 index 00000000..4113e38a --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/asterisk-to-range-conversion-test.js @@ -0,0 +1,12 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression/asterisk-to-range-conversion'); + +describe('asterisk-to-range-conversion.js', () => { + it('shuld convert * to ranges', () => { + const expressions = '* * * * * *'.split(' '); + const expression = conversion(expressions).join(' '); + expect(expression).to.equal('0-59 0-59 0-23 1-31 1-12 0-6'); + }); +}); diff --git a/node_modules/node-cron/test/convert-expression/convert-expression-test.js b/node_modules/node-cron/test/convert-expression/convert-expression-test.js new file mode 100644 index 00000000..7ce30e39 --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/convert-expression-test.js @@ -0,0 +1,18 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression'); + +describe('month-names-conversion.js', () => { + it('shuld convert month names', () => { + const expression = conversion('* * * * January,February *'); + const expressions = expression.split(' '); + expect(expressions[4]).to.equal('1,2'); + }); + + it('shuld convert week day names', () => { + const expression = conversion('* * * * * Mon,Sun'); + const expressions = expression.split(' '); + expect(expressions[5]).to.equal('1,0'); + }); +}); diff --git a/node_modules/node-cron/test/convert-expression/month-names-conversion-test.js b/node_modules/node-cron/test/convert-expression/month-names-conversion-test.js new file mode 100644 index 00000000..6e10c84c --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/month-names-conversion-test.js @@ -0,0 +1,16 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression/month-names-conversion'); + +describe('month-names-conversion.js', () => { + it('shuld convert month names', () => { + const months = conversion('January,February,March,April,May,June,July,August,September,October,November,December'); + expect(months).to.equal('1,2,3,4,5,6,7,8,9,10,11,12'); + }); + + it('shuld convert month names', () => { + const months = conversion('Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'); + expect(months).to.equal('1,2,3,4,5,6,7,8,9,10,11,12'); + }); +}); diff --git a/node_modules/node-cron/test/convert-expression/range-conversion-test.js b/node_modules/node-cron/test/convert-expression/range-conversion-test.js new file mode 100644 index 00000000..a8f85f05 --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/range-conversion-test.js @@ -0,0 +1,24 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression/range-conversion'); + +describe('range-conversion.js', () => { + it('shuld convert ranges to numbers', () => { + const expressions = '0-3 0-3 0-2 1-3 1-2 0-3'.split(' '); + const expression = conversion(expressions).join(' '); + expect(expression).to.equal('0,1,2,3 0,1,2,3 0,1,2 1,2,3 1,2 0,1,2,3'); + }); + + it('shuld convert ranges to numbers', () => { + const expressions = '0-3 0-3 8-10 1-3 1-2 0-3'.split(' '); + const expression = conversion(expressions).join(' '); + expect(expression).to.equal('0,1,2,3 0,1,2,3 8,9,10 1,2,3 1,2 0,1,2,3'); + }); + + it('should convert comma delimited ranges to numbers', () => { + var expressions = '0-2,10-23'.split(' '); + var expression = conversion(expressions).join(' '); + expect(expression).to.equal('0,1,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23'); + }); +}); diff --git a/node_modules/node-cron/test/convert-expression/step-values-conversion-test.js b/node_modules/node-cron/test/convert-expression/step-values-conversion-test.js new file mode 100644 index 00000000..9221d962 --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/step-values-conversion-test.js @@ -0,0 +1,19 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression/step-values-conversion'); + +describe('step-values-conversion.js', () => { + it('should convert step values', () => { + var expressions = '1,2,3,4,5,6,7,8,9,10/2 0,1,2,3,4,5,6,7,8,9/5 * * * *'.split(' '); + expressions = conversion(expressions); + expect(expressions[0]).to.equal('2,4,6,8,10'); + expect(expressions[1]).to.equal('0,5'); + }); + + it('should throw an error if step value is not a number', () => { + var expressions = '1,2,3,4,5,6,7,8,9,10/someString 0,1,2,3,4,5,6,7,8,9/5 * * * *'.split(' '); + expect(() => conversion(expressions)).to.throw('someString is not a valid step value'); + }); + +}); diff --git a/node_modules/node-cron/test/convert-expression/week-day-names-conversion-test.js b/node_modules/node-cron/test/convert-expression/week-day-names-conversion-test.js new file mode 100644 index 00000000..5c5a41a5 --- /dev/null +++ b/node_modules/node-cron/test/convert-expression/week-day-names-conversion-test.js @@ -0,0 +1,21 @@ +'use strict'; + +const { expect } = require('chai'); +const conversion = require('../../src/convert-expression/week-day-names-conversion'); + +describe('week-day-names-conversion.js', () => { + it('shuld convert week day names names', () => { + const weekDays = conversion('Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'); + expect(weekDays).to.equal('1,2,3,4,5,6,0'); + }); + + it('shuld convert short week day names names', () => { + const weekDays = conversion('Mon,Tue,Wed,Thu,Fri,Sat,Sun'); + expect(weekDays).to.equal('1,2,3,4,5,6,0'); + }); + + it('shuld convert 7 to 0', () => { + const weekDays = conversion('7'); + expect(weekDays).to.equal('0'); + }); +}); diff --git a/node_modules/node-cron/test/node-cron-test.js b/node_modules/node-cron/test/node-cron-test.js new file mode 100644 index 00000000..d65de1e4 --- /dev/null +++ b/node_modules/node-cron/test/node-cron-test.js @@ -0,0 +1,138 @@ +const { assert } = require('chai'); +const sinon = require('sinon'); +const cron = require('../src/node-cron'); + +describe('node-cron', () => { + beforeEach(() => { + this.clock = sinon.useFakeTimers(new Date(2018, 0, 1, 0, 0, 0, 0)); + }); + + afterEach(() => { + this.clock.restore(); + }); + + describe('schedule', () => { + it('should schedule a task', () => { + let executed = 0; + cron.schedule('* * * * * *', () => { + executed += 1; + }); + + this.clock.tick(2000); + + assert.equal(2, executed); + }); + + it('should schedule a task with America/Sao_Paulo timezone', (done) => { + let startDate = new Date('Thu, 20 Sep 2018 00:00:00.000Z'); + this.clock.restore(); + this.clock = sinon.useFakeTimers(startDate); + cron.schedule('* * * * * *', (date) => { + assert.equal(19, date.getDate()); + assert.equal(8, date.getMonth()); + assert.equal(2018, date.getFullYear()); + assert.equal(21, date.getHours()); + assert.equal(0, date.getMinutes()); + assert.equal(1, date.getSeconds()); + done(); + }, { + timezone: 'America/Sao_Paulo' + }); + this.clock.tick(1000); + }); + + it('should schedule a task with Europe/Rome timezone', (done) => { + let startDate = new Date('Thu, 20 Sep 2018 00:00:00.000Z'); + this.clock.restore(); + this.clock = sinon.useFakeTimers(startDate); + cron.schedule('* * * * * *', (date) => { + assert.equal(20, date.getDate()); + assert.equal(8, date.getMonth()); + assert.equal(2018, date.getFullYear()); + assert.equal(2, date.getHours()); + assert.equal(0, date.getMinutes()); + assert.equal(1, date.getSeconds()); + done(); + }, { + timezone: 'Europe/Rome' + }); + this.clock.tick(1000); + }); + + it('should schedule a task stoped', () => { + let executed = 0; + cron.schedule('* * * * * *', () => { + executed += 1; + }, { scheduled: false }); + + this.clock.tick(2000); + + assert.equal(0, executed); + }); + + it('should start a stoped task', () => { + let executed = 0; + let scheduledTask = cron.schedule('* * * * * *', () => { + executed += 1; + }, { scheduled: false }); + + this.clock.tick(2000); + assert.equal(0, executed); + scheduledTask.start(); + this.clock.tick(2000); + assert.equal(2, executed); + }); + + it('should recover missed executions', (done) => { + let executed = 0; + this.clock.restore(); + let scheduledTask = cron.schedule('* * * * * *', () => { + executed += 1; + }, { recoverMissedExecutions: true }); + + let wait = true; + let startedAt = new Date(); + + while(wait){ + if((new Date().getTime() - startedAt.getTime()) > 1000){ + wait = false; + } + } + + setTimeout(() => { + scheduledTask.stop(); + assert.equal(2, executed); + done(); + }, 1000); + }).timeout(4000); + + it('should schedule a background task', () => { + let task = cron.schedule('* * * * * *', './test/assets/dummy-task.js'); + assert.isNotNull(task); + assert.isDefined(task); + assert.isTrue(task.isRunning()); + task.stop(); + }); + }); + + describe('validate', () => { + it('should validate a pattern', () => { + assert.isTrue(cron.validate('* * * * * *')); + }); + + it('should fail with a invalid pattern', () => { + assert.isFalse(cron.validate('62 * * * * *')); + }); + }); + + describe('getTasks', () => { + beforeEach(() => { + global.scheduledTasks = new Map(); + }); + + it('should store a task', () => { + cron.schedule('* * * * *', () => {}); + assert.lengthOf(cron.getTasks(), 1); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/node-cron/test/pattern-validation/validate-day-test.js b/node_modules/node-cron/test/pattern-validation/validate-day-test.js new file mode 100644 index 00000000..f272a3c2 --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-day-test.js @@ -0,0 +1,32 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate day of month', () => { + it('should fail with invalid day of month', () => { + expect(() => { + validate('* * 32 * *'); + }).to.throw('32 is a invalid expression for day of month'); + }); + + it('should not fail with valid day of month', () => { + expect(() => { + validate('0 * * 15 * *'); + }).to.not.throw(); + }); + + it('should not fail with * for day of month', () => { + expect(() => { + validate('* * * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2 for day of month', () => { + expect(() => { + validate('* * */2 * *'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-hours-test.js b/node_modules/node-cron/test/pattern-validation/validate-hours-test.js new file mode 100644 index 00000000..8dc3ef48 --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-hours-test.js @@ -0,0 +1,38 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate hour', () => { + it('should fail with invalid hour', () => { + expect(() => { + validate('* 25 * * *'); + }).to.throw('25 is a invalid expression for hour'); + }); + + it('should not fail with valid hour', () => { + expect(() => { + validate('* 12 * * *'); + }).to.not.throw(); + }); + + it('should not fail with * for hour', () => { + expect(() => { + validate('* * * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2 for hour', () => { + expect(() => { + validate('* */2 * * *'); + }).to.not.throw(); + }); + + it('should accept range for hours', () => { + expect(() => { + validate('* 3-20 * * *'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-minute-test.js b/node_modules/node-cron/test/pattern-validation/validate-minute-test.js new file mode 100644 index 00000000..063287ba --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-minute-test.js @@ -0,0 +1,32 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate minutes', () => { + it('should fail with invalid minute', () => { + expect(() => { + validate('63 * * * *'); + }).to.throw('63 is a invalid expression for minute'); + }); + + it('should not fail with valid minute', () => { + expect(() => { + validate('30 * * * *'); + }).to.not.throw(); + }); + + it('should not fail with *', () => { + expect(() => { + validate('* * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2', () => { + expect(() => { + validate('*/2 * * * *'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-month-test.js b/node_modules/node-cron/test/pattern-validation/validate-month-test.js new file mode 100644 index 00000000..c139be38 --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-month-test.js @@ -0,0 +1,44 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate month', () => { + it('should fail with invalid month', () => { + expect( () => { + validate('* * * 13 *'); + }).to.throw('13 is a invalid expression for month'); + }); + + it('should fail with invalid month name', () => { + expect( () => { + validate('* * * foo *'); + }).to.throw('foo is a invalid expression for month'); + }); + + it('should not fail with valid month', () => { + expect( () => { + validate('* * * 10 *'); + }).to.not.throw(); + }); + + it('should not fail with valid month name', () => { + expect( () => { + validate('* * * September *'); + }).to.not.throw(); + }); + + it('should not fail with * for month', () => { + expect( () => { + validate('* * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2 for month', () => { + expect( () => { + validate('* * * */2 *'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-pattern-type.js b/node_modules/node-cron/test/pattern-validation/validate-pattern-type.js new file mode 100644 index 00000000..34ba825c --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-pattern-type.js @@ -0,0 +1,19 @@ +'use strict'; + +const { expect } = require('chai'); +const Task = require('../../src/task'); + +describe('Task', () => { + it('should accept a function', () => { + expect(() => { + new Task(() => {}); + }).to.not.throw(); + }); + + it('should fail without a function', () => { + expect(() => { + new Task([]); + }).to.throw('execution must be a function'); + }); + +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-second-test.js b/node_modules/node-cron/test/pattern-validation/validate-second-test.js new file mode 100644 index 00000000..c65c137d --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-second-test.js @@ -0,0 +1,32 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate seconds', () => { + it('should fail with invalid second', () => { + expect(() => { + validate('63 * * * * *'); + }).to.throw('63 is a invalid expression for second'); + }); + + it('should not fail with valid second', () => { + expect(() => { + validate('30 * * * * *'); + }).to.not.throw(); + }); + + it('should not fail with * for second', () => { + expect(() => { + validate('* * * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2 for second', () => { + expect(() => { + validate('*/2 * * * * *'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-test.js b/node_modules/node-cron/test/pattern-validation/validate-test.js new file mode 100644 index 00000000..8a586147 --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-test.js @@ -0,0 +1,24 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation', () => { + it('should succeed with a valid expression', () => { + expect(() => { + validate('59 * * * *'); + }).to.not.throw(); + }); + + it('should fail with an invalid expression', () => { + expect(() => { + validate('60 * * * *'); + }).to.throw('60 is a invalid expression for minute'); + }); + + it('should fail without a string', () => { + expect(() => { + validate(50); + }).to.throw('pattern must be a string!'); + }); +}); diff --git a/node_modules/node-cron/test/pattern-validation/validate-week-day-test.js b/node_modules/node-cron/test/pattern-validation/validate-week-day-test.js new file mode 100644 index 00000000..a396776a --- /dev/null +++ b/node_modules/node-cron/test/pattern-validation/validate-week-day-test.js @@ -0,0 +1,56 @@ +'use strict'; + +const { expect } = require('chai'); +const validate = require('../../src/pattern-validation'); + +describe('pattern-validation.js', () => { + describe('validate week day', () => { + it('should fail with invalid week day', () => { + expect(() => { + validate('* * * * 9'); + }).to.throw('9 is a invalid expression for week day'); + }); + + it('should fail with invalid week day name', () => { + expect(() => { + validate('* * * * foo'); + }).to.throw('foo is a invalid expression for week day'); + }); + + it('should not fail with valid week day', () => { + expect(() => { + validate('* * * * 5'); + }).to.not.throw(); + }); + + it('should not fail with valid week day name', () => { + expect(() => { + validate('* * * * Friday'); + }).to.not.throw(); + }); + + it('should not fail with * for week day', () => { + expect(() => { + validate('* * * * *'); + }).to.not.throw(); + }); + + it('should not fail with */2 for week day', () => { + expect(() => { + validate('* * * */2 *'); + }).to.not.throw(); + }); + + it('should not fail with Monday-Sunday for week day', () => { + expect(() => { + validate('* * * * Monday-Sunday'); + }).to.not.throw(); + }); + + it('should not fail with 1-7 for week day', () => { + expect(() => { + validate('0 0 1 1 1-7'); + }).to.not.throw(); + }); + }); +}); diff --git a/node_modules/node-cron/test/scheduled-task-test.js b/node_modules/node-cron/test/scheduled-task-test.js new file mode 100644 index 00000000..1636e3b7 --- /dev/null +++ b/node_modules/node-cron/test/scheduled-task-test.js @@ -0,0 +1,91 @@ +const { assert } = require('chai'); +const sinon = require('sinon'); +const ScheduledTask = require('../src/scheduled-task'); + +describe('ScheduledTask', () => { + beforeEach(() => { + this.clock = sinon.useFakeTimers(new Date(2018, 0, 1, 0, 0, 0, 0)); + }); + + afterEach(() => { + this.clock.restore(); + }); + + it('should start a task by default', (done) => { + let executed = 0; + let scheduledTask = new ScheduledTask('* * * * * *', () => { + executed += 1; + }); + this.clock.tick(3000); + assert.equal(3, executed); + scheduledTask.stop(); + done(); + }); + + it('should create a task stoped', (done) => { + let executed = 0; + let scheduledTask = new ScheduledTask('* * * * * *', () => { + executed += 1; + }, { scheduled: false }); + this.clock.tick(3000); + assert.equal(0, executed); + scheduledTask.stop(); + done(); + }); + + it('should start a task', (done) => { + let executed = 0; + let scheduledTask = new ScheduledTask('* * * * * *', () => { + executed += 1; + }, { scheduled: false }); + this.clock.tick(3000); + assert.equal(0, executed); + scheduledTask.start(); + this.clock.tick(3000); + assert.equal(3, executed); + scheduledTask.stop(); + done(); + }); + + it('should stop a task', () => { + let executed = 0; + let scheduledTask = new ScheduledTask('* * * * * *', () => { + executed += 1; + }, { scheduled: true }); + this.clock.tick(3000); + assert.equal(3, executed); + scheduledTask.stop(); + this.clock.tick(3000); + assert.equal(3, executed); + }); + + it('should create a task stopped and run it once created', () => { + let executed = 0; + new ScheduledTask('* * * * * *', () => { + executed += 1; + }, { scheduled: false, runOnInit: true }); + this.clock.tick(3000); + assert.equal(1, executed); + }); + + it('should create a task stopped and run it once manually', () => { + let executed = 0; + let scheduledTask = new ScheduledTask('* * * * * *', () => { + executed += 1; + }, { scheduled: false }); + this.clock.tick(3000); + assert.equal(0, executed); + scheduledTask.now(); + assert.equal(1, executed); + }); + + it('should emit event every minute', () => { + let executed = 0; + let scheduledTask = new ScheduledTask('0 * * * * *', () => { + executed += 1; + }, { scheduled: true }); + this.clock.tick(60000 * 3); + assert.equal(3, executed); + scheduledTask.stop(); + }); +}); diff --git a/node_modules/node-cron/test/scheduler-test.js b/node_modules/node-cron/test/scheduler-test.js new file mode 100644 index 00000000..166b30d5 --- /dev/null +++ b/node_modules/node-cron/test/scheduler-test.js @@ -0,0 +1,91 @@ +const { assert } = require('chai'); +const sinon = require('sinon'); +const Scheduler = require('../src/scheduler'); + +describe('Scheduler', () => { + beforeEach(() => { + this.clock = sinon.useFakeTimers(); + }); + + afterEach(() => { + this.clock.restore(); + }); + + it('should emit an event on matched time', (done) => { + let scheduler = new Scheduler('* * * * * *'); + + scheduler.on('scheduled-time-matched', (date) => { + assert.isNotNull(date); + assert.instanceOf(date, Date); + scheduler.stop(); + done(); + }); + + scheduler.start(); + this.clock.tick(1000); + }); + + it('should emit an event every second', (done) => { + let scheduler = new Scheduler('* * * * * *'); + let emited = 0; + scheduler.on('scheduled-time-matched', (date) => { + emited += 1; + assert.isNotNull(date); + assert.instanceOf(date, Date); + if(emited === 5){ + scheduler.stop(); + done(); + } + }); + scheduler.start(); + this.clock.tick(5000); + }); + + it('should recover missed executions', (done) => { + this.clock.restore(); + let scheduler = new Scheduler('* * * * * *', null, true); + let emited = 0; + scheduler.on('scheduled-time-matched', () => { + emited += 1; + }); + scheduler.start(); + let wait = true; + let startedAt = new Date(); + + while(wait){ + if((new Date().getTime() - startedAt.getTime()) > 1000){ + wait = false; + } + } + + setTimeout(() => { + scheduler.stop(); + assert.equal(2, emited); + done(); + }, 1000); + }).timeout(3000); + + it('should ignore missed executions', (done) => { + this.clock.restore(); + let scheduler = new Scheduler('* * * * * *', null, false); + let emited = 0; + scheduler.on('scheduled-time-matched', () => { + emited += 1; + }); + scheduler.start(); + let wait = true; + let startedAt = new Date(); + + while(wait){ + if((new Date().getTime() - startedAt.getTime()) > 1000){ + wait = false; + } + } + + setTimeout(() => { + scheduler.stop(); + assert.equal(1, emited); + done(); + }, 1000); + }).timeout(3000); +}); \ No newline at end of file diff --git a/node_modules/node-cron/test/storage-test.js b/node_modules/node-cron/test/storage-test.js new file mode 100644 index 00000000..b7a73161 --- /dev/null +++ b/node_modules/node-cron/test/storage-test.js @@ -0,0 +1,26 @@ +const { assert } = require('chai'); +const storage = require('../src/storage'); + +describe('storage', () => { + it('should store a task', () => { + global.scheduledTasks = new Map(); + storage.save({}); + assert.lengthOf(global.scheduledTasks, 1); + }); + + it('should get all tasks', () => { + global.scheduledTasks = new Map(); + global.scheduledTasks.set(0, {}); + assert.lengthOf(storage.getTasks(), 1); + }); + + describe('on import', () => { + it('should keep stored items across imports', () => { + delete require.cache[require.resolve('../src/storage')]; + global.scheduledTasks = new Map(); + storage.save({}); + let storage2 = require('../src/storage'); + assert.lengthOf(storage2.getTasks(), 1); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/node-cron/test/task-test.js b/node_modules/node-cron/test/task-test.js new file mode 100644 index 00000000..0f866e2f --- /dev/null +++ b/node_modules/node-cron/test/task-test.js @@ -0,0 +1,49 @@ +const { assert } = require('chai'); +const sinon = require('sinon'); +const Task = require('../src/task'); + +describe('Task', () => { + beforeEach(() => { + this.clock = sinon.useFakeTimers(new Date(2018, 0, 1, 0, 0, 0, 0)); + }); + + afterEach(() => { + this.clock.restore(); + }); + + it('should emit event on finish a task', async () => { + let finished = false; + let task = new Task(() => 'ok'); + task.on('task-finished', () => finished = true); + await task.execute(); + assert.equal(true, finished); + }); + + it('should emit event on error a task', async () => { + let error; + let task = new Task(() => { + throw Error('execution error'); + }); + task.on('task-failed', (err) => error = err.message); + await task.execute(); + assert.equal('execution error', error); + }); + + it('should emit event on finish a promise task', async () => { + let finished = false; + const promise = () => new Promise((resolve) => resolve('ok')); + let task = new Task(promise); + task.on('task-finished', () => finished = true); + await task.execute(); + assert.equal(true, finished); + }); + + it('should emit event on error a promise task', async () => { + let failed = false; + const promise = () => new Promise((resolve, reject) => reject('errou')); + const task = new Task(promise); + task.on('task-failed', (error) => failed = error); + await task.execute(); + assert.equal('errou', failed); + }); +}); \ No newline at end of file diff --git a/node_modules/node-cron/test/time-matcher-test.js b/node_modules/node-cron/test/time-matcher-test.js new file mode 100644 index 00000000..f7b55630 --- /dev/null +++ b/node_modules/node-cron/test/time-matcher-test.js @@ -0,0 +1,244 @@ +const { assert } = require('chai'); +const TimeMatcher = require('../src/time-matcher'); +const moment = require('moment-timezone'); + +describe('TimeMatcher', () => { + describe('wildcard', () => { + it('should accept wildcard for second', () => { + let matcher = new TimeMatcher('* * * * * *'); + assert.isTrue(matcher.match(new Date())); + }); + + it('should accept wildcard for minute', () => { + let matcher = new TimeMatcher('0 * * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 10, 20, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 10, 20, 1))); + }); + + it('should accept wildcard for hour', () => { + let matcher = new TimeMatcher('0 0 * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 10, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 10, 1, 0))); + }); + + it('should accept wildcard for day', () => { + let matcher = new TimeMatcher('0 0 0 * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 1, 0, 0))); + }); + + it('should accept wildcard for month', () => { + let matcher = new TimeMatcher('0 0 0 1 * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 2, 0, 0, 0))); + }); + + it('should accept wildcard for week day', () => { + let matcher = new TimeMatcher('0 0 0 1 4 *'); + assert.isTrue(matcher.match(new Date(2018, 3, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 3, 2, 0, 0, 0))); + }); + }); + + describe('single value', () => { + it('should accept single value for second', () => { + let matcher = new TimeMatcher('5 * * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 5))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 0, 6))); + }); + + it('should accept single value for minute', () => { + let matcher = new TimeMatcher('0 5 * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 5, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 6, 0))); + }); + + it('should accept single value for hour', () => { + let matcher = new TimeMatcher('0 0 5 * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 5, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 6, 0, 0))); + }); + + it('should accept single value for day', () => { + let matcher = new TimeMatcher('0 0 0 5 * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 5, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 6, 0, 0, 0))); + }); + + it('should accept single value for month', () => { + let matcher = new TimeMatcher('0 0 0 1 5 *'); + assert.isTrue(matcher.match(new Date(2018, 4, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 5, 1, 0, 0, 0))); + }); + + it('should accept single value for week day', () => { + let matcher = new TimeMatcher('0 0 0 * * monday'); + assert.isTrue(matcher.match(new Date(2018, 4, 7, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 4, 8, 0, 0, 0))); + }); + }); + + describe('multiple values', () => { + it('should accept multiple values for second', () => { + let matcher = new TimeMatcher('5,6 * * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 5))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 6))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 0, 7))); + }); + + it('should accept multiple values for minute', () => { + let matcher = new TimeMatcher('0 5,6 * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 5, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 6, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 7, 0))); + }); + + it('should accept multiple values for hour', () => { + let matcher = new TimeMatcher('0 0 5,6 * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 5, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 6, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 7, 0, 0))); + }); + + it('should accept multiple values for day', () => { + let matcher = new TimeMatcher('0 0 0 5,6 * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 5, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 6, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 7, 0, 0, 0))); + }); + + it('should accept multiple values for month', () => { + let matcher = new TimeMatcher('0 0 0 1 may,june *'); + assert.isTrue(matcher.match(new Date(2018, 4, 1, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 5, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 6, 1, 0, 0, 0))); + }); + + it('should accept multiple values for week day', () => { + let matcher = new TimeMatcher('0 0 0 * * monday,tue'); + assert.isTrue(matcher.match(new Date(2018, 4, 7, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 4, 8, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 4, 9, 0, 0, 0))); + }); + }); + + describe('range', () => { + it('should accept range for second', () => { + let matcher = new TimeMatcher('5-7 * * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 5))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 6))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 7))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 0, 8))); + }); + + it('should accept range for minute', () => { + let matcher = new TimeMatcher('0 5-7 * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 5, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 6, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 7, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 8, 0))); + }); + + it('should accept range for hour', () => { + let matcher = new TimeMatcher('0 0 5-7 * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 5, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 6, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 7, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 8, 0, 0))); + }); + + it('should accept range for day', () => { + let matcher = new TimeMatcher('0 0 0 5-7 * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 5, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 6, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 7, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 8, 0, 0, 0))); + }); + + it('should accept range for month', () => { + let matcher = new TimeMatcher('0 0 0 1 may-july *'); + assert.isTrue(matcher.match(new Date(2018, 4, 1, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 5, 1, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 6, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 7, 1, 0, 0, 0))); + }); + + it('should accept range for week day', () => { + let matcher = new TimeMatcher('0 0 0 * * monday-wed'); + assert.isTrue(matcher.match(new Date(2018, 4, 7, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 4, 8, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 4, 9, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 4, 10, 0, 0, 0))); + }); + }); + + describe('step values', () => { + it('should accept step values for second', () => { + let matcher = new TimeMatcher('*/2 * * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 2))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 0, 6))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 0, 7))); + }); + + it('should accept step values for minute', () => { + let matcher = new TimeMatcher('0 */2 * * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 2, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 0, 6, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 0, 7, 0))); + }); + + it('should accept step values for hour', () => { + let matcher = new TimeMatcher('0 0 */2 * * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 2, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 1, 6, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 1, 7, 0, 0))); + }); + + it('should accept step values for day', () => { + let matcher = new TimeMatcher('0 0 0 */2 * *'); + assert.isTrue(matcher.match(new Date(2018, 0, 2, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 0, 6, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 0, 7, 0, 0, 0))); + }); + + it('should accept step values for month', () => { + let matcher = new TimeMatcher('0 0 0 1 */2 *'); + assert.isTrue(matcher.match(new Date(2018, 1, 1, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 5, 1, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 6, 1, 0, 0, 0))); + }); + + it('should accept step values for week day', () => { + let matcher = new TimeMatcher('0 0 0 * * */2'); + assert.isTrue(matcher.match(new Date(2018, 4, 6, 0, 0, 0))); + assert.isTrue(matcher.match(new Date(2018, 4, 8, 0, 0, 0))); + assert.isFalse(matcher.match(new Date(2018, 4, 9, 0, 0, 0))); + }); + }); + + describe('timezone', ()=>{ + it('should match with timezone America/Sao_Paulo', () => { + let matcher = new TimeMatcher('0 0 0 * * *', 'America/Sao_Paulo'); + let utcTime = new Date('Thu Oct 11 2018 03:00:00Z'); + assert.isTrue(matcher.match(utcTime)); + }); + + it('should match with timezone Europe/Rome', () => { + let matcher = new TimeMatcher('0 0 0 * * *', 'Europe/Rome'); + let utcTime = new Date('Thu Oct 11 2018 22:00:00Z'); + assert.isTrue(matcher.match(utcTime)); + }); + + it('should match with all available timezone of moment-timezone', () => { + const allTimeZone = moment.tz.names(); + for (let zone in allTimeZone) { + const tmp = moment(); + const expected = moment.tz(tmp,allTimeZone[zone]); + const pattern = expected.second() + ' ' + expected.minute() + ' ' + expected.hour() + ' ' + expected.date() + ' ' + (expected.month()+1) + ' ' + expected.day(); + const matcher = new TimeMatcher(pattern, allTimeZone[zone]); + const utcTime = new Date(tmp.year(), tmp.month(), tmp.date(), tmp.hour(), tmp.minute(), tmp.second(), tmp.millisecond()); + assert.isTrue(matcher.match(utcTime)); + } + }); + }); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a26206a6..73753dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "mongodb": "^5.0.1", "mongoose": "^5.11.8", "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.2", "node-schedule": "^2.1.1", "nodemon": "^2.0.20", "nunjucks": "^3.2.3", @@ -7578,6 +7579,17 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -16537,6 +16549,14 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "requires": { + "uuid": "8.3.2" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", diff --git a/package.json b/package.json index 716c95e3..f6a1bf88 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "mongodb": "^5.0.1", "mongoose": "^5.11.8", "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.2", "node-schedule": "^2.1.1", "nodemon": "^2.0.20", "nunjucks": "^3.2.3", diff --git a/src/controllers/tanksController.js b/src/controllers/tanksController.js index 5c636073..20c032f1 100644 --- a/src/controllers/tanksController.js +++ b/src/controllers/tanksController.js @@ -7,7 +7,7 @@ const fastify = require("fastify")({ logger: true, }); // const tanksController = require("./tanksController") - +const cron = require('node-cron'); const moment = require('moment'); @@ -187,7 +187,7 @@ exports.updateTanklevels = async (req, reply) => { // waterLevel = newWaterLevel; - }, 2000); + }, 5000); intervals[tankId] = intervalId; } @@ -250,13 +250,11 @@ exports.getTanklevels = async (req, reply) => { }; const intervals = {}; -let sum_oh_count = 0 -let bore_sump_count = 0 -let sump_sump_count = 0 + exports.motorAction = async (req, reply) => { try { - //let start_time,stop_time + let start_time,stop_time const customerId = req.params.customerId; const action = req.body.action @@ -272,7 +270,7 @@ exports.motorAction = async (req, reply) => { const interval_variable = supplier_tank+receiver_tank if(action === "start"){ - const start_time = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'}) + start_time = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'}) // const stop_at = req.body.stop_at @@ -294,9 +292,7 @@ exports.motorAction = async (req, reply) => { await overheadTank.save(); } - sum_oh_count++; - console.log(sum_oh_count) - + // await changingfrom_tankwaterlevel(customerId,initial_update,supplier_tank_info); // let supplier_waterlevel = parseInt(supplier_tank_info.waterlevel.replace(/,/g, ''), 10) @@ -310,19 +306,15 @@ exports.motorAction = async (req, reply) => { let supplier_waterlevel = parseInt(supplier_tank_info2.waterlevel.replace(/,/g, ''), 10) const newWaterLevel = receiver_waterlevel + 250//Math.floor(supplier_waterlevel * 0.1); - const newSupplierWaterLevel = supplier_waterlevel//Math.floor(supplier_waterlevel * 0.1); + const newSupplierWaterLevel = supplier_waterlevel-250//Math.floor(supplier_waterlevel * 0.1); const supplier_capacity = parseInt(supplier_tank_info.capacity.replace(/,/g, ''), 10) console.log((newSupplierWaterLevel/supplier_capacity)*100) // Check if updating should stop if ((newSupplierWaterLevel/supplier_capacity)*100 <= 5 || (newWaterLevel/receiver_capacity)*100 >= 95 || (newWaterLevel/receiver_capacity)*100 >= desired_water_percentage || rcvr_info.motor_status === "0") { + console.log((newSupplierWaterLevel/supplier_capacity)*100,(newWaterLevel/receiver_capacity)*100,(newWaterLevel/receiver_capacity)*100,) clearInterval(intervals[interval_variable]); // Clear the interval for this tank delete intervals[interval_variable]; - sum_oh_count--; - if (sum_oh_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "0" } }); - - - } + const overheadTank = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); const connection = overheadTank.connections.inputConnections.find((conn) => conn.inputConnections === supplier_tank); @@ -330,6 +322,21 @@ exports.motorAction = async (req, reply) => { connection.motor_status = "0"; await overheadTank.save(); } + // Find the tank based on customerId, tankName, and tankLocation + const tankToUpdate = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); + +// Check if all objects in inputConnections have motor_status === "0" + const allMotorStatusZero = tankToUpdate.connections.inputConnections.every(connection => connection.motor_status === "0"); + + if (allMotorStatusZero) { + // Update the motor_status field to "0" for the tank + await Tank.findOneAndUpdate( + { customerId, tankName: receiver_tank, tankLocation: receiver_type }, + { $set: { motor_status: "0" } } + ); + } + + console.log("end for"+receiver_tank); } else { @@ -338,12 +345,14 @@ exports.motorAction = async (req, reply) => { // receiver_waterlevel = newWaterLevel; console.log((newSupplierWaterLevel/supplier_capacity)*100) // console.log((newWaterLevel/receiver_capacity)*100) - console.log(receiver_tank+""+newWaterLevel+""+"bore to sump") + console.log(newWaterLevel+""+receiver_type) + console.log(newSupplierWaterLevel+""+supplier_tank) + await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { waterlevel: newWaterLevel } }) + await Tank.findOneAndUpdate({customerId, tankName: supplier_tank,tankLocation:supplier_tank_type}, { $set: { waterlevel: newSupplierWaterLevel } }) + //if (supplier_tank_info2.motor_status==="0"){ - supplier_waterlevel = parseInt(supplier_tank_info2.waterlevel.replace(/,/g, ''), 10)-250 - console.log(supplier_tank+""+newSupplierWaterLevel+""+"s to oh") - await Tank.findOneAndUpdate({customerId, tankName: supplier_tank,tankLocation:supplier_tank_type}, { $set: { waterlevel: supplier_waterlevel } }) + // await Tank.findOneAndUpdate({customerId, tankName: supplier_tank,tankLocation:supplier_tank_type}, { $set: { waterlevel: supplier_waterlevel } }) // } } @@ -417,7 +426,6 @@ exports.motorAction = async (req, reply) => { connection.motor_status = "1"; await sumpTank.save(); } - sump_sump_count++; // console.log(receiver_capacity,"0",receiver_tank_info.tankName) await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "1" } }); @@ -441,11 +449,26 @@ exports.motorAction = async (req, reply) => { clearInterval(intervals[interval_variable]); // Clear the interval for this tank delete intervals[interval_variable]; - sump_sump_count--; - if (sump_sump_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "0" } }); - } - + const sumpTank = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); + const connection = sumpTank.connections.inputConnections.find((conn) => conn.inputConnections === supplier_tank); + + if (connection) { + connection.motor_status = "0"; + await sumpTank.save(); + } + } + const tankToUpdate = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); + + // Check if all objects in inputConnections have motor_status === "0" + const allMotorStatusZero = tankToUpdate.connections.inputConnections.every(connection => connection.motor_status === "0"); + + if (allMotorStatusZero) { + // Update the motor_status field to "0" for the tank + await Tank.findOneAndUpdate( + { customerId, tankName: receiver_tank, tankLocation: receiver_type }, + { $set: { motor_status: "0" } } + ); + console.log("end for" + receiver_tank); } else { // Update water levels in database @@ -473,7 +496,6 @@ exports.motorAction = async (req, reply) => { if(supplier_tank_type==="bore" && receiver_type === "sump"){ const receiver_capacity = parseInt(receiver_tank_info.capacity.replace(/,/g, ''), 10) // console.log(receiver_capacity,"0",receiver_tank_info.tankName) - bore_sump_count++; await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "1" } }); const sumpTank = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); const connection = sumpTank.connections.inputConnections.find((conn) => conn.inputConnections === supplier_tank); @@ -500,18 +522,29 @@ exports.motorAction = async (req, reply) => { clearInterval(intervals[interval_variable]); // Clear the interval for this tank delete intervals[interval_variable]; - bore_sump_count--; - if (bore_sump_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "0" } }); - } const sumpTank = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); const connection = sumpTank.connections.inputConnections.find((conn) => conn.inputConnections === supplier_tank); - - if (connection) { - connection.motor_status = "0"; - await sumpTank.save(); - } + + if (connection) { + connection.motor_status = "0"; + await sumpTank.save(); + } + + const tankToUpdate = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); + +// Check if all objects in inputConnections have motor_status === "0" + const allMotorStatusZero = tankToUpdate.connections.inputConnections.every(connection => connection.motor_status === "0"); + + if (allMotorStatusZero) { + // Update the motor_status field to "0" for the tank + await Tank.findOneAndUpdate( + { customerId, tankName: receiver_tank, tankLocation: receiver_type }, + { $set: { motor_status: "0" } } + ); + } + + console.log("end for" + receiver_tank); } else { // Update water levels in database @@ -526,77 +559,14 @@ exports.motorAction = async (req, reply) => { } // console.log(customerId,req.body.from,req.body.from_type,receiver_tank,req.body.to_type,) - motorData = { - - customerId:customerId, - supplierTank : req.body.from, - supplier_type: req.body.from_type, - receiverTank: receiver_tank, - receiver_type: req.body.to_type, - startTime: req.body.startTime, - stopTime: req.body.stopTime, - - - - }; - var motorData = new MotorData(motorData); - - checkFormEncoding = isUserFormUrlEncoded(req); - if (checkFormEncoding.isUserFormUrlEncoded) { - usertobeInserted = checkFormEncoding.motorData; - console.log("thsi true url string"); - motorData.customerId = customerId; - motorData.supplierTank = req.body.from; - motorData.receiverTank = receiver_tank; - motorData.supplier_type = req.body.from_type; - motorData.receiver_type = req.body.to_type; - motorData.startTime = usertobeInserted.startTime; - motorData.stopTime = usertobeInserted.stopTime; - - } - const motor_data = await motorData.save(); - console.log(motor_data) - // reply.send({ status_code: 200, data: motor_data }); - - - - - reply.send({ status_code: 200, "start time": start_time, data: motor_data}); - console.log(start_time) - return motor_data - - } else if (action === "stop") { - const stop_time = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'}) + stop_time = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'}) clearInterval(intervals[interval_variable]); // Clear the interval for this tank delete intervals[interval_variable]; - if (supplier_tank_type === "sump" && receiver_type ==="sump"){ - - sump_sump_count--; - if (sump_sump_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:receiver_type}, { $set: { motor_status: "0" } }); - } - - - } - if (supplier_tank_type === "sump"){ - sum_oh_count--; - if (sum_oh_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:(req.body.to_type).toLowerCase()}, { $set: { motor_status: "0" } }); - - } - } - if (supplier_tank_type === "bore"){ - bore_sump_count--; - if (bore_sump_count===0){ - await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:(req.body.to_type).toLowerCase()}, { $set: { motor_status: "0" } }); - - } - } const overheadTank = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); const connection = overheadTank.connections.inputConnections.find((conn) => conn.inputConnections === supplier_tank); @@ -605,12 +575,70 @@ exports.motorAction = async (req, reply) => { connection.motor_status = "0"; await overheadTank.save(); } + + const tankToUpdate = await Tank.findOne({ customerId, tankName: receiver_tank, tankLocation: receiver_type }); + + // Check if all objects in inputConnections have motor_status === "0" + const allMotorStatusZero = tankToUpdate.connections.inputConnections.every(connection => connection.motor_status === "0"); + + if (allMotorStatusZero) { + // Update the motor_status field to "0" for the tank + await Tank.findOneAndUpdate( + { customerId, tankName: receiver_tank, tankLocation: receiver_type }, + { $set: { motor_status: "0" } } + ); + } + + motorData = { + + + + customerId:customerId, + supplierTank : supplier_tank, + supplier_type: supplier_tank_type, + receiverTank: receiver_tank, + receiver_type: receiver_type, + // startTime: startTime, + stopTime: req.body.stopTime, + + + + }; + var motorData = new MotorData(motorData); + + checkFormEncoding = isUserFormUrlEncoded(req); + if (checkFormEncoding.isUserFormUrlEncoded) { + usertobeInserted = checkFormEncoding.motorData; + console.log("thsi true url string"); + motorData.customerId = customerId; + motorData.supplierTank = supplierTank; + motorData.receiverTank = receiver_tank; + motorData.supplier_type = supplier_type; + motorData.receiver_type = receiver_type; + //motorData.startTime = startTime; + motorData.stopTime = stop_time; + + + } + const motor_data = await motorData.save(); + console.log(motor_data) + // reply.send({ status_code: 200, data: motor_data }); + + + + + // reply.send({ status_code: 200, "start time": start_time, data: motor_data}); + console.log(start_time) + // return motor_data + + + // console.log(stop_time) // clearInterval(intervalId); // await Tank.findOneAndUpdate({customerId, tankName: receiver_tank,tankLocation:(req.body.to_type).toLowerCase()}, { $set: { motor_status: "0" } }); - reply.send({ status_code: 200, "stop time": stop_time}); + reply.send({ status_code: 200, "stop time": stop_time,data: motor_data}); } else { throw new Error("Invalid action"); } @@ -1006,3 +1034,34 @@ exports.startUpdateLoop = async (request, reply) => { } }, updateInterval); }; + +exports.updatewaterlevelsatmidnight = async (req, reply) => { + try { + // Schedule the task to run every day at 10 seconds past the minute + cron.schedule('0 0 * * *', async () => { + try { + const tanks = await Tank.find({ customerId: req.query.customerId }); + for (const tank of tanks) { + tank.waterlevel_at_midnight = tank.waterlevel; + console.log(tank.waterlevel_at_midnight) + await tank.save(); + } + console.log('Waterlevel noted in waterlevel_at_midnight'); + } catch (error) { + console.error('Error occurred:', error); + } + }); + + await Tank.find({ customerId: req.query.customerId }) + .exec() + .then((docs) => { + reply.send({ status_code: 200, data: docs, count: docs.length }); + }) + .catch((err) => { + console.log(err); + reply.send({ error: err }); + }); + } catch (err) { + throw boom.boomify(err); + } +}; diff --git a/src/models/tanks.js b/src/models/tanks.js index 5184d731..83274ac9 100644 --- a/src/models/tanks.js +++ b/src/models/tanks.js @@ -42,6 +42,7 @@ const tanksSchema = new mongoose.Schema({ waterlevel: { type: String, default: "0" }, tankLocation: { type: String, default: null }, motor_status: { type: String, default: "0" }, + waterlevel_at_midnight:{ type: String,default:"0" }, connections: { source: { type: String }, inputConnections: [ diff --git a/src/routes/tanksRoute.js b/src/routes/tanksRoute.js index fb52a6c3..49543548 100644 --- a/src/routes/tanksRoute.js +++ b/src/routes/tanksRoute.js @@ -506,7 +506,23 @@ module.exports = function (fastify, opts, next) { handler: tanksController.startUpdateLoop, }); - + fastify.get("/api/updatewaterlevelsatmidnight", { + schema: { + tags: ["Tank"], + description: "This is for storing waterlevels in tank at midnight", + summary: "This is for storing waterlevels in tank at midnight", + querystring: { + customerId: {type: 'string'} + }, + security: [ + { + basicAuth: [], + }, + ], + }, + preHandler: fastify.auth([fastify.authenticate]), + handler: tanksController.updatewaterlevelsatmidnight, + }); next(); }