diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e4dd495..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - "parserOptions": { - "ecmaVersion": 12, - "env": { - "node": true, - "es2021": true - } - } -} \ No newline at end of file diff --git a/config/default.js b/config/default.js index f4b2d9b..9286c5d 100644 --- a/config/default.js +++ b/config/default.js @@ -11,13 +11,13 @@ module.exports = { dialect: 'postgres', logging: false, dialectOptions: { - ssl: process.env.DATABASE_SSL != null, + ssl: process.env.DATABASE_SSL != null }, pool: { max: 5, min: 0, - idle: 10000, - }, + idle: 10000 + } }, DISABLE_LOGGING: process.env.DISABLE_LOGGING || 'false', @@ -29,8 +29,9 @@ module.exports = { KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID, KAFKA_CLIENT_CERT: process.env.KAFKA_CLIENT_CERT ? process.env.KAFKA_CLIENT_CERT.replace('\\n', '\n') : null, - KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ? - process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') : null, + KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY + ? process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') + : null, // mapping from event type to sendgrid email template id TEMPLATE_MAP: process.env.TEMPLATE_MAP, @@ -41,16 +42,16 @@ module.exports = { EMAIL_MAX_ERRORS: process.env.EMAIL_MAX_ERRORS || 2, EMAIL_PAUSE_TIME: process.env.EMAIL_PAUSE_TIME || 30, - //in every 2 minutes will retry for failed status + // in every 2 minutes will retry for failed status EMAIL_RETRY_SCHEDULE: process.env.EMAIL_RETRY_SCHEDULE || '0 */2 * * * *', - //wont't retry failed emails older than this time (msec) + // wont't retry failed emails older than this time (msec) EMAIL_RETRY_MAX_AGE: process.env.EMAIL_RETRY_MAX_AGE || 1000 * 60 * 60 * 24, API_CONTEXT_PATH: process.env.API_CONTEXT_PATH || '/v5/email', PAYLOAD_SENDGRID_TEMPLATE_KEY: process.env.PAYLOAD_SENDGRID_TEMPLATE_KEY || 'sendgrid_template_id', - //Tracing information + // Tracing information APM_OTLP_TRACE_EXPORTER_URL: process.env.APM_OTLP_TRACE_EXPORTER_URL || '', APM_SERVICE_NAME: process.env.APM_SERVICE_NAME || 'tc-email-service-svc', - APM_TRACER_NAME: process.env.APM_TRACER_NAME || 'tc-email-service-svc', -}; + APM_TRACER_NAME: process.env.APM_TRACER_NAME || 'tc-email-service-svc' +} diff --git a/config/development.js b/config/development.js index af6397c..3bbbb7d 100644 --- a/config/development.js +++ b/config/development.js @@ -3,5 +3,5 @@ */ module.exports = { LOG_LEVEL: process.env.LOG_LEVEL || 'debug', - PORT: process.env.PORT || 4000, -}; + PORT: process.env.PORT || 4000 +} diff --git a/config/production.js b/config/production.js index 162d717..43d2a39 100644 --- a/config/production.js +++ b/config/production.js @@ -4,5 +4,5 @@ module.exports = { LOG_LEVEL: process.env.LOG_LEVEL || 'error', PORT: process.env.PORT || 4000, - DISABLE_LOGGING: process.env.DISABLE_LOGGING || 'true', -}; + DISABLE_LOGGING: process.env.DISABLE_LOGGING || 'true' +} diff --git a/config/test.js b/config/test.js index 32f39d9..8b1cd28 100644 --- a/config/test.js +++ b/config/test.js @@ -3,5 +3,5 @@ */ module.exports = { LOG_LEVEL: process.env.LOG_LEVEL || 'debug', - PORT: process.env.PORT || 4000, -}; + PORT: process.env.PORT || 4000 +} diff --git a/connect/connectEmailServer.js b/connect/connectEmailServer.js index 81b76af..c231043 100644 --- a/connect/connectEmailServer.js +++ b/connect/connectEmailServer.js @@ -1,15 +1,15 @@ /** * This is TopCoder connect email server. */ -const _ = require('lodash'); -const config = require('config'); -const emailServer = require('../index'); -const service = require('./service'); -const logger = require('../src/common/logger'); +const _ = require('lodash') +const config = require('config') +const emailServer = require('../index') +const service = require('./service') +const logger = require('../src/common/logger') // set configuration for the server, see ../config/default.js for available config parameters // setConfig should be called before initDatabase and start functions -emailServer.setConfig({ LOG_LEVEL: config.LOG_LEVEL }); +emailServer.setConfig({ LOG_LEVEL: config.LOG_LEVEL }) // add topic handlers, // handler is used build a notification list for a message of a topic, @@ -17,26 +17,25 @@ emailServer.setConfig({ LOG_LEVEL: config.LOG_LEVEL }); // the topic is topic name, // the message is JSON event message, const handler = async (topic, message) => { - let templateId = config.TEMPLATE_MAP[topic]; - templateId = _.get(message, config.PAYLOAD_SENDGRID_TEMPLATE_KEY, templateId); + let templateId = config.TEMPLATE_MAP[topic] + templateId = _.get(message, config.PAYLOAD_SENDGRID_TEMPLATE_KEY, templateId) if (!templateId) { - return { success: false, error: `Template not found for topic ${topic}` }; + return { success: false, error: `Template not found for topic ${topic}` } } try { await service.sendEmail(templateId, message) - return { success: true }; + return { success: true } } catch (err) { - logger.error("Error occurred in sendgrid api calling:", err); - return { success: false, error: err }; + logger.error('Error occurred in sendgrid api calling:', err) + return { success: false, error: err } } - -}; +} // init all events _.keys(config.TEMPLATE_MAP).forEach((eventType) => { - emailServer.addTopicHandler(eventType, handler); -}); + emailServer.addTopicHandler(eventType, handler) +}) // init database, it will clear and re-create all tables emailServer @@ -47,9 +46,9 @@ emailServer emailServer.start() }) .catch((e) => { - logger.error('Error occurred in starting email server:', e); - process.exit(1); - }); // eslint-disable-line no-console + logger.error('Error occurred in starting email server:', e) + process.exit(1) + }) // eslint-disable-line no-console // if no need to init database, then directly start the server: -// emailServer.start() \ No newline at end of file +// emailServer.start() diff --git a/connect/service.js b/connect/service.js index 4d6c248..1179034 100644 --- a/connect/service.js +++ b/connect/service.js @@ -2,28 +2,28 @@ * This is TopCoder connect email service. */ -const sgMail = require('@sendgrid/mail'); -const config = require('config'); -const logger = require('../src/common/logger'); +const sgMail = require('@sendgrid/mail') +const config = require('config') +const logger = require('../src/common/logger') // set api key for SendGrid email client -sgMail.setApiKey(config.SENDGRID_API_KEY); +sgMail.setApiKey(config.SENDGRID_API_KEY) const sendEmail = async (templateId, message) => { // send email - const span = await logger.startSpan('sendEmail'); + const span = await logger.startSpan('sendEmail') let msg = {} - const from = message.from ? message.from : config.EMAIL_FROM; - const replyTo = message.replyTo ? message.replyTo : config.EMAIL_FROM; - const substitutions = message.data; - const categories = message.categories ? message.categories : []; - const to = message.recipients; - const cc = message.cc ? message.cc : []; - const bcc = message.bcc ? message.bcc : []; - const sendAt = message.sendAt ? message.sendAt : undefined; + const from = message.from ? message.from : config.EMAIL_FROM + const replyTo = message.replyTo ? message.replyTo : config.EMAIL_FROM + const substitutions = message.data + const categories = message.categories ? message.categories : [] + const to = message.recipients + const cc = message.cc ? message.cc : [] + const bcc = message.bcc ? message.bcc : [] + const sendAt = message.sendAt ? message.sendAt : undefined const personalizations = message.personalizations ? message.personalizations : undefined - const attachments = message.attachments ? message.attachments : []; + const attachments = message.attachments ? message.attachments : [] - if (message.version && message.version == "v3") { + if (message.version && message.version === 'v3') { msg = { to, templateId, @@ -36,7 +36,7 @@ const sendEmail = async (templateId, message) => { // send email bcc, attachments, sendAt - }; + } } else { msg = { to, @@ -47,23 +47,23 @@ const sendEmail = async (templateId, message) => { // send email replyTo, categories, cc, - bcc, - }; + bcc + } } - logger.info(`Sending email with templateId: ${templateId} and message: ${JSON.stringify(msg)}`); + logger.info(`Sending email with templateId: ${templateId} and message: ${JSON.stringify(msg)}`) try { - await logger.endSpan(span); - const sgSpan = await logger.startSpan('sendgrid'); + await logger.endSpan(span) + const sgSpan = await logger.startSpan('sendgrid') const result = await sgMail.send(msg) - await logger.endSpan(sgSpan); - logger.info(`Email sent successfully with result: ${JSON.stringify(result)} \n ${JSON.stringify(msg)}`); + await logger.endSpan(sgSpan) + logger.info(`Email sent successfully with result: ${JSON.stringify(result)} \n ${JSON.stringify(msg)}`) return result } catch (err) { - logger.error(`Error occurred in sendgrid api calling: ${err}`); + logger.error(`Error occurred in sendgrid api calling: ${err}`) throw err } } module.exports = { - sendEmail, + sendEmail } -logger.buildService(module.exports) \ No newline at end of file +logger.buildService(module.exports) diff --git a/index.js b/index.js index 3b93e9e..5c6d9a7 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,18 @@ require('dotenv').config() -const config = require('config'); -const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; -const express = require('express'); -const _ = require('lodash'); -const cors = require('cors'); -const bodyParser = require('body-parser'); -const schedule = require('node-schedule'); -const helper = require('./src/common/helper'); -const logger = require('./src/common/logger'); -const errors = require('./src/common/errors'); -const models = require('./src/models'); -const { initServer, retryEmail } = require('./src/init'); - -config.TEMPLATE_MAP = JSON.parse(config.TEMPLATE_MAP); +const config = require('config') +const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator +const express = require('express') +const _ = require('lodash') +const cors = require('cors') +const bodyParser = require('body-parser') +const schedule = require('node-schedule') +const helper = require('./src/common/helper') +const logger = require('./src/common/logger') +const errors = require('./src/common/errors') +const models = require('./src/models') +const { initServer, retryEmail } = require('./src/init') + +config.TEMPLATE_MAP = JSON.parse(config.TEMPLATE_MAP) // key is topic name, e.g. 'notifications.connect.project.created'; // value is handler for the topic to find user ids that should receive notifications for a message, @@ -20,7 +20,7 @@ config.TEMPLATE_MAP = JSON.parse(config.TEMPLATE_MAP); // the topic is topic name, // the message is JSON event message, // the callback is function(error, userIds), where userIds is an array of user ids to receive notifications -const handlers = {}; +const handlers = {} /** * Set configuration, the default config will be overridden by the given config, @@ -30,30 +30,30 @@ const handlers = {}; * * @param {Object} cfg the configuration to set */ -function setConfig(cfg) { +function setConfig (cfg) { if (!cfg) { - throw new errors.ValidationError('Missing configuration.'); + throw new errors.ValidationError('Missing configuration.') } - _.extend(config, cfg); + _.extend(config, cfg) } /** * Remove topic handler for topic. * @param {String} topic the topic name */ -function removeTopicHandler(topic) { +function removeTopicHandler (topic) { if (!topic) { - throw new errors.ValidationError('Missing topic.'); + throw new errors.ValidationError('Missing topic.') } - delete handlers[topic]; + delete handlers[topic] } /** * Get all topic handlers. * @returns {Object} all topic handlers, key is topic name, value is handler */ -function getAllHandlers() { - return handlers; +function getAllHandlers () { + return handlers } /** @@ -61,87 +61,85 @@ function getAllHandlers() { * @param {String} topic the topic name * @param {Function} handler the handler */ -function addTopicHandler(topic, handler) { +function addTopicHandler (topic, handler) { if (!topic) { - throw new errors.ValidationError('Missing topic.'); + throw new errors.ValidationError('Missing topic.') } if (!handler) { - throw new errors.ValidationError('Missing handler.'); + throw new errors.ValidationError('Missing handler.') } - handlers[topic] = handler; + handlers[topic] = handler } -const app = express(); -app.set('port', config.PORT); +const app = express() +app.set('port', config.PORT) -app.use(cors()); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); +app.use(cors()) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: true })) -const apiRouter = express.Router(); +const apiRouter = express.Router() // load all routes _.each(require('./src/routes'), (verbs, url) => { _.each(verbs, (def, verb) => { - const actions = []; - const method = require('./src/controllers/' + def.controller)[def.method]; + const actions = [] + const method = require('./src/controllers/' + def.controller)[def.method] if (!method) { - throw new Error(def.method + ' is undefined'); + throw new Error(def.method + ' is undefined') } actions.push((req, res, next) => { - req.signature = `${def.controller}#${def.method}`; - next(); - }); + req.signature = `${def.controller}#${def.method}` + next() + }) if (url !== '/health') { - actions.push(jwtAuth()); + actions.push(jwtAuth()) actions.push((req, res, next) => { if (!req.authUser) { - return next(new errors.UnauthorizedError('Authorization failed.')); + return next(new errors.UnauthorizedError('Authorization failed.')) } - req.user = req.authUser; - return next(); - }); + req.user = req.authUser + return next() + }) } - actions.push(method); - apiRouter[verb](url, helper.autoWrapExpress(actions)); - }); -}); + actions.push(method) + apiRouter[verb](url, helper.autoWrapExpress(actions)) + }) +}) -app.use(config.API_CONTEXT_PATH, apiRouter); +app.use(config.API_CONTEXT_PATH, apiRouter) app.use((req, res) => { - res.status(404).json({ error: 'route not found' }); -}); + res.status(404).json({ error: 'route not found' }) +}) app.use((err, req, res) => { // eslint-disable-line - logger.logFullError(err, req.signature); - let status = err.httpStatus || 500; + logger.logFullError(err, req.signature) + let status = err.httpStatus || 500 if (err.isJoi) { - status = 400; + status = 400 } // from express-jwt if (err.name === 'UnauthorizedError') { - status = 401; + status = 401 } - res.status(status); + res.status(status) if (err.isJoi) { res.json({ error: 'Validation failed', - details: err.details, - }); + details: err.details + }) } else { res.json({ - error: err.message, - }); + error: err.message + }) } -}); - - -function start() { +}) +function start () { initServer(handlers).then(() => { if (_.isEmpty(handlers)) { - throw new errors.ValidationError('Missing handler(s).'); + throw new errors.ValidationError('Missing handler(s).') } schedule.scheduleJob(config.EMAIL_RETRY_SCHEDULE, async function () { @@ -149,16 +147,15 @@ function start() { await retryEmail(handlers) } catch (err) { console.log('Error in retrying email', err) - logger.error(err); + logger.error(err) } - }); + }) app.listen(app.get('port'), () => { - logger.info(`Express server listening on port ${app.get('port')}`); - }); + logger.info(`Express server listening on port ${app.get('port')}`) + }) }).catch((err) => { - - logger.error(err); - process.exit(1); + logger.error(err) + process.exit(1) }) } @@ -166,12 +163,12 @@ function start() { * Initialize database. All tables are cleared and re-created. * @returns {Promise} promise to init db */ -async function initDatabase() { +async function initDatabase () { // load models only after config is set - logger.info('Initializing database...'); - const span = await logger.startSpan('initDatabase'); - await models.init(true); - await logger.endSpan(span); + logger.info('Initializing database...') + const span = await logger.startSpan('initDatabase') + await models.init(true) + await logger.endSpan(span) } // Exports @@ -181,7 +178,7 @@ module.exports = { removeTopicHandler, getAllHandlers, start, - initDatabase, -}; + initDatabase +} -logger.buildService(module.exports) \ No newline at end of file +logger.buildService(module.exports) diff --git a/package-lock.json b/package-lock.json index 5fca655..2a2e943 100644 --- a/package-lock.json +++ b/package-lock.json @@ -752,6 +752,18 @@ "es-shim-unscopables": "^1.0.0" } }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -931,6 +943,15 @@ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, "bunyan": { "version": "1.8.15", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", @@ -1394,6 +1415,23 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + } + } + }, "es-abstract": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz", @@ -1545,6 +1583,18 @@ } } }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", + "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", + "dev": true + }, "eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -1598,6 +1648,33 @@ } } }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "eslint-plugin-import": { "version": "2.26.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", @@ -1630,6 +1707,78 @@ } } }, + "eslint-plugin-n": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz", + "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==", + "dev": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.10.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.7" + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.31.10", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", + "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "eslint-scope": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", @@ -2035,6 +2184,12 @@ "resolved": "https://registry.npmjs.org/get-parameter-names/-/get-parameter-names-0.3.0.tgz", "integrity": "sha512-KkR1dX7U1TynXFkqveVE/XoRn9qRAsM2q4Eu2WsGTFzoaSdnNQEfxbcK+LMv8DcFoQQT9BFjNL+bf9ZyTLkWpg==" }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2098,6 +2253,12 @@ "slash": "^3.0.0" } }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -2551,6 +2712,12 @@ "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", "dev": true }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2565,6 +2732,12 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -2631,6 +2804,16 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -2703,6 +2886,27 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "dependencies": { + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2806,6 +3010,15 @@ "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loupe": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", @@ -3150,6 +3363,27 @@ "es-abstract": "^1.19.1" } }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -3217,6 +3451,12 @@ "p-limit": "^3.0.2" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -3231,6 +3471,16 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3340,6 +3590,67 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + } + } + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -3374,6 +3685,17 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", @@ -3497,6 +3819,12 @@ "unpipe": "1.0.0" } }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -3884,6 +4212,34 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" }, + "standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.0.0.tgz", + "integrity": "sha512-GlCM9nzbLUkr+TYR5I2WQoIah4wHA2lMauqbyPLV/oI5gJxqhHzhjl9EG2N0lr/nRqI3KCbCvm/W3smxvLaChA==", + "dev": true, + "requires": { + "eslint": "^8.13.0", + "eslint-config-standard": "17.0.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.1.0", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "^7.28.0", + "standard-engine": "^15.0.0" + } + }, + "standard-engine": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.0.0.tgz", + "integrity": "sha512-4xwUhJNo1g/L2cleysUqUv7/btn7GEbYJvmgKrQ2vd/8pkTmN8cpqAZg+BT8Z1hNeEH787iWUdOpL8fmApLtxA==", + "dev": true, + "requires": { + "get-stdin": "^8.0.0", + "minimist": "^1.2.6", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3899,6 +4255,22 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -4411,6 +4783,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", diff --git a/package.json b/package.json index c9f3947..c0f8a24 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "node connect/connectEmailServer", "test": "NODE_ENV=test mocha", - "lint": "eslint *.js src config connect || true" + "lint": "standard --fix" }, "author": "TCSCODER", "license": "none", @@ -15,7 +15,8 @@ "eslint": "^8.23.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.26.0", - "mocha": "^10.0.0" + "mocha": "^10.0.0", + "standard": "^17.0.0" }, "dependencies": { "@sendgrid/client": "^7.7.0", diff --git a/src/common/errors.js b/src/common/errors.js index 5f066ad..1b33cc0 100644 --- a/src/common/errors.js +++ b/src/common/errors.js @@ -2,7 +2,7 @@ * This file defines application errors. */ -const util = require('util'); +const util = require('util') /** * Helper function to create generic error object with http status code @@ -11,24 +11,24 @@ const util = require('util'); * @returns {Function} the error constructor * @private */ -function _createError(name, statusCode) { +function _createError (name, statusCode) { /** * The error constructor * @param {String} message the error message * @param {String} [cause] the error cause * @constructor */ - function ErrorCtor(message, cause) { - Error.call(this); - Error.captureStackTrace(this); - this.message = message || name; - this.cause = cause; - this.httpStatus = statusCode; + function ErrorCtor (message, cause) { + Error.call(this) + Error.captureStackTrace(this) + this.message = message || name + this.cause = cause + this.httpStatus = statusCode } - //TODO: upgrade to ES6 - util.inherits(ErrorCtor, Error); - ErrorCtor.prototype.name = name; - return ErrorCtor; + // TODO: upgrade to ES6 + util.inherits(ErrorCtor, Error) + ErrorCtor.prototype.name = name + return ErrorCtor } module.exports = { @@ -36,5 +36,5 @@ module.exports = { ValidationError: _createError('ValidationError', 400), UnauthorizedError: _createError('UnauthorizedError', 401), ForbiddenError: _createError('ForbiddenError', 403), - NotFoundError: _createError('NotFoundError', 404), -}; + NotFoundError: _createError('NotFoundError', 404) +} diff --git a/src/common/helper.js b/src/common/helper.js index 0d5cfc6..f72cda7 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -2,22 +2,21 @@ * Contains generic helper methods */ -const _ = require('lodash'); +const _ = require('lodash') /** * Wrap generator function to standard express function * @param {Function} fn the generator function * @returns {Function} the wrapped function */ -function wrapExpress(fn) { +function wrapExpress (fn) { return async function (req, res, next) { try { await fn(req, res, next) } catch (e) { - next + next() }; - }; - + } } /** @@ -25,23 +24,23 @@ function wrapExpress(fn) { * @param obj the object (controller exports) * @returns {Object|Array} the wrapped object */ -function autoWrapExpress(obj) { +function autoWrapExpress (obj) { if (_.isArray(obj)) { - return obj.map(autoWrapExpress); + return obj.map(autoWrapExpress) } if (_.isFunction(obj)) { if (obj.constructor.name === 'GeneratorFunction') { - return wrapExpress(obj); + return wrapExpress(obj) } - return obj; + return obj } _.each(obj, (value, key) => { - obj[key] = autoWrapExpress(value); - }); - return obj; + obj[key] = autoWrapExpress(value) + }) + return obj } module.exports = { wrapExpress, - autoWrapExpress, -}; + autoWrapExpress +} diff --git a/src/controllers/HealthController.js b/src/controllers/HealthController.js index 79578ac..eee34b0 100644 --- a/src/controllers/HealthController.js +++ b/src/controllers/HealthController.js @@ -7,11 +7,11 @@ * @param req the request * @param res the response */ -function health(req, res) { - res.json({ health: 'ok' }); +function health (req, res) { + res.json({ health: 'ok' }) } // Exports module.exports = { - health, -}; + health +} diff --git a/src/init.js b/src/init.js index b180693..06516e6 100644 --- a/src/init.js +++ b/src/init.js @@ -2,18 +2,17 @@ * The application entry point */ -const config = require('config'); -const _ = require('lodash'); +const config = require('config') +const _ = require('lodash') const { Kafka } = require('kafkajs') -const logger = require('./common/logger'); -const models = require('./models'); - +const logger = require('./common/logger') +const models = require('./models') /** * Configure Kafka consumer. * @param {Object} handlers the handlers */ -async function configureKafkaConsumer(handlers) { +async function configureKafkaConsumer (handlers) { // create group consumer let brokers = [''] if (config.KAFKA_URL.startsWith('ssl://')) { @@ -21,83 +20,81 @@ async function configureKafkaConsumer(handlers) { } else { brokers = config.KAFKA_URL.split(',') } - const options = { brokers }; + const options = { brokers } if (config.KAFKA_CLIENT_CERT && config.KAFKA_CLIENT_CERT_KEY) { - options.ssl = { cert: config.KAFKA_CLIENT_CERT, key: config.KAFKA_CLIENT_CERT_KEY }; + options.ssl = { cert: config.KAFKA_CLIENT_CERT, key: config.KAFKA_CLIENT_CERT_KEY } } const kafka = new Kafka(options) - const consumer = kafka.consumer({ groupId: config.KAFKA_GROUP_ID }); + const consumer = kafka.consumer({ groupId: config.KAFKA_GROUP_ID }) await consumer.connect() - await consumer.subscribe({ topics: _.keys(handlers) }); + await consumer.subscribe({ topics: _.keys(handlers) }) dataHandler(consumer, handlers).catch((err) => { - console.log('error', 'Kafka consumer error', err); - logger.error(err); - }); + console.log('error', 'Kafka consumer error', err) + logger.error(err) + }) } - -async function dataHandler(consumer, handlers) { +async function dataHandler (consumer, handlers) { try { await consumer.run({ eachMessage: async (data) => { - const span = await logger.startSpan('dataHandler'); + const span = await logger.startSpan('dataHandler') const topic = data.topic const msg = data.message const partition = data.partition - //If there is no message, return + // If there is no message, return if (!msg) return const message = msg.value.toString('utf8') - logger.info(`Handle Kafka event message; Topic: ${topic}; Partition: ${partition}; Message: ${message}.`); + logger.info(`Handle Kafka event message; Topic: ${topic}; Partition: ${partition}; Message: ${message}.`) // ignore configured Kafka topic prefix - let topicName = topic; + const topicName = topic // find handler - const handler = handlers[topicName]; + const handler = handlers[topicName] if (!handler) { - logger.info(`No handler configured for topic: ${topicName}`); + logger.info(`No handler configured for topic: ${topicName}`) // return null to ignore this message - return null; + return null } const emailModel = await models.loadEmailModule() - const busPayload = JSON.parse(message); - const messageJSON = busPayload.payload; + const busPayload = JSON.parse(message) + const messageJSON = busPayload.payload try { const emailInfo = { status: 'PENDING', topicName, data: JSON.stringify(messageJSON), - recipients: JSON.stringify(messageJSON.recipients), + recipients: JSON.stringify(messageJSON.recipients) } const emailObj = await emailModel.create(emailInfo) - const result = await handler(topicName, messageJSON); + const result = await handler(topicName, messageJSON) logger.info('info', 'Email sent', { sender: 'Connect', to_address: messageJSON.recipients.join(','), from_address: config.EMAIL_FROM, status: result.success ? 'Message accepted' : 'Message rejected', - error: result.error ? result.error.toString() : 'No error message', - }); + error: result.error ? result.error.toString() : 'No error message' + }) const emailTries = {} if (result.success) { - emailTries[topicName] = 0; - emailObj.status = 'SUCCESS'; - await emailObj.save(); + emailTries[topicName] = 0 + emailObj.status = 'SUCCESS' + await emailObj.save() } else { - // emailTries[topicName] += 1; //temporary disabling this feature + // emailTries[topicName] += 1; //temporary disabling this feature if (result.error) { - logger.error('error', 'Send email error details', result.error); + logger.error('error', 'Send email error details', result.error) } } - await logger.endSpan(span); + await logger.endSpan(span) } catch (e) { - await logger.endSpanWithError(span, e); + await logger.endSpanWithError(span, e) logger.error(e) - } - - }, + } + } }) } catch (e) { logger.error(e) @@ -127,64 +124,59 @@ async function dataHandler(consumer, handlers) { process.kill(process.pid, type) } }) - }) - - - + }) } - /** * Callback to retry sending email. * @param {Object} handlers the handlers */ -async function retryEmail(handlers) { - const span = await logger.startSpan('retryEmail'); +async function retryEmail (handlers) { + const span = await logger.startSpan('retryEmail') const loader = await models.loadEmailModule() const emailModel = await loader.findAll({ where: { status: 'FAILED', createdAt: { $gt: new Date(new Date() - config.EMAIL_RETRY_MAX_AGE) } } }) if (emailModel.length > 0) { - logger.info(`Found ${emailModel.length} e-mails to be resent`); + logger.info(`Found ${emailModel.length} e-mails to be resent`) emailModel.map(async m => { // find handler - const handler = handlers[m.topicName]; + const handler = handlers[m.topicName] if (!handler) { - logger.warn(`No handler configured for topic: ${m.topicName}`); - await logger.endSpan(span); - return m; + logger.warn(`No handler configured for topic: ${m.topicName}`) + await logger.endSpan(span) + return m } - const messageJSON = { data: JSON.parse(m.data), recipients: JSON.parse(m.recipients) }; - const result = await handler(m.topicName, messageJSON); + const messageJSON = { data: JSON.parse(m.data), recipients: JSON.parse(m.recipients) } + const result = await handler(m.topicName, messageJSON) if (result.success) { - logger.info(`Email model with ${m.id} was sent correctly`); - m.status = 'SUCCESS'; - await logger.endSpan(span); - return m.save(); + logger.info(`Email model with ${m.id} was sent correctly`) + m.status = 'SUCCESS' + await logger.endSpan(span) + return m.save() } - logger.info(`Email model with ${m.id} wasn't sent correctly`); - await logger.endSpan(span); - return m; - }); + logger.info(`Email model with ${m.id} wasn't sent correctly`) + await logger.endSpan(span) + return m + }) } else { - await logger.endSpan(span); - return models; - } - + await logger.endSpan(span) + return models + } } -async function initServer(handlers) { +async function initServer (handlers) { + const span = await logger.startSpan('initServer') try { - const span = await logger.startSpan('initServer'); await models.init() await configureKafkaConsumer(handlers) - await logger.endSpan(span); + await logger.endSpan(span) } catch (e) { - await logger.endSpanWithError(span, e); + await logger.endSpanWithError(span, e) } } // Exports module.exports = { initServer, - retryEmail, -}; + retryEmail +} -logger.buildService(module.exports) \ No newline at end of file +logger.buildService(module.exports) diff --git a/src/models/datasource.js b/src/models/datasource.js index 5b41d13..12d70bf 100644 --- a/src/models/datasource.js +++ b/src/models/datasource.js @@ -9,33 +9,33 @@ * @version 2.0 */ -const config = require('config'); -const Sequelize = require('sequelize'); -const logger = require('../common/logger'); +const config = require('config') +const Sequelize = require('sequelize') +const logger = require('../common/logger') -let sequelizeInstance = null; +let sequelizeInstance = null /** * get sequelize instance */ -async function getSequelize() { +async function getSequelize () { if (!sequelizeInstance) { - sequelizeInstance = new Sequelize(config.DATABASE_URL, config.DATABASE_OPTIONS); - const span = await logger.startSpan('getSequelize'); + sequelizeInstance = new Sequelize(config.DATABASE_URL, config.DATABASE_OPTIONS) + const span = await logger.startSpan('getSequelize') try { await sequelizeInstance.authenticate() - await logger.endSpan(span); - logger.info('Database connection has been established successfully.'); + await logger.endSpan(span) + logger.info('Database connection has been established successfully.') } catch (e) { - await logger.endSpanWithErr(span, e); - logger.error('Unable to connect to the database:', err); + await logger.endSpanWithErr(span, e) + logger.error('Unable to connect to the database:', e) } } - return sequelizeInstance; + return sequelizeInstance } module.exports = { - getSequelize, -}; + getSequelize +} -logger.buildService(module.exports) \ No newline at end of file +logger.buildService(module.exports) diff --git a/src/models/index.js b/src/models/index.js index 1ea3fa5..b02993d 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -2,51 +2,48 @@ * Copyright (C) 2022TopCoder Inc., All Rights Reserved. */ +const sequelizeInstance = require('./datasource') +const DataTypes = require('sequelize/lib/data-types') +const logger = require('../common/logger') - -const sequelizeInstance = require('./datasource'); -const DataTypes = require('sequelize/lib/data-types'); -const logger = require('../common/logger'); - -async function defineEmailModel(sequelize, DataTypes) { - const span = await logger.startSpan('defineEmailModel'); +async function defineEmailModel (sequelize, DataTypes) { + const span = await logger.startSpan('defineEmailModel') const Email = sequelize.define('Email', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, topicName: { type: DataTypes.STRING, allowNull: true, field: 'topic_name' }, data: { type: DataTypes.TEXT, allowNull: false }, recipients: { type: DataTypes.STRING, allowNull: false }, - status: { type: DataTypes.STRING, allowNull: false }, - }); - await Email.sync(); - await logger.endSpan(span); - return Email; + status: { type: DataTypes.STRING, allowNull: false } + }) + await Email.sync() + await logger.endSpan(span) + return Email } -async function loadSequelizeModule() { - const span = await logger.startSpan('loadSequelizeModule'); - const res = await sequelizeInstance.getSequelize(); - await logger.endSpan(span); - return res; +async function loadSequelizeModule () { + const span = await logger.startSpan('loadSequelizeModule') + const res = await sequelizeInstance.getSequelize() + await logger.endSpan(span) + return res } -async function loadEmailModule() { - const span = await logger.startSpan('loadEmailModule'); - const sequelize = await loadSequelizeModule(); - await logger.endSpan(span); - return defineEmailModel(sequelize, DataTypes); +async function loadEmailModule () { + const span = await logger.startSpan('loadEmailModule') + const sequelize = await loadSequelizeModule() + await logger.endSpan(span) + return defineEmailModel(sequelize, DataTypes) } -async function init() { - logger.info("Initializing models"); - const span = await logger.startSpan('init'); - const sequelize = await loadSequelizeModule(); - await sequelize.sync(); - await logger.endSpan(span); +async function init () { + logger.info('Initializing models') + const span = await logger.startSpan('init') + const sequelize = await loadSequelizeModule() + await sequelize.sync() + await logger.endSpan(span) } - module.exports = { loadEmailModule, - init, -}; + init +} -logger.buildService(module.exports) \ No newline at end of file +logger.buildService(module.exports) diff --git a/src/routes.js b/src/routes.js index 53714d9..4b4d545 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,7 +3,7 @@ module.exports = { '/health': { get: { controller: 'HealthController', - method: 'health', - }, - }, -}; + method: 'health' + } + } +} diff --git a/test/emailServiceTest.js b/test/emailServiceTest.js index 75b7ae9..a4b43c6 100644 --- a/test/emailServiceTest.js +++ b/test/emailServiceTest.js @@ -1,21 +1,18 @@ /** * Email test. */ +/* eslint-env mocha */ +const _ = require('lodash') +const config = require('config') +const Kafka = require('no-kafka') -const _ = require('lodash'); -const sgMail = require('@sendgrid/mail'); -const config = require('config'); -const Kafka = require('no-kafka'); - -const emailServer = require('../index'); -const service = require('../connect/service'); - -let globalDone = null; +const emailServer = require('../index') +const service = require('../connect/service') const defaultHandler = async (topic, message, callback) => { - const templateId = config.TEMPLATE_MAP[topic]; + const templateId = config.TEMPLATE_MAP[topic] if (templateId === undefined) { - return { success: false, error: `Template not found for topic ${topic}` }; + return { success: false, error: `Template not found for topic ${topic}` } } try { // send email @@ -28,17 +25,8 @@ const defaultHandler = async (topic, message, callback) => { // init all events _.keys(config.TEMPLATE_MAP).forEach((eventType) => { - emailServer.addTopicHandler(eventType, defaultHandler); -}); - -const finish = (err) => { - if (globalDone) { - setTimeout(() => { - globalDone(err); - globalDone = null; - }, 10); - } -} + emailServer.addTopicHandler(eventType, defaultHandler) +}) const successTestMessage = { data: { @@ -50,39 +38,36 @@ const successTestMessage = { } describe('Email Test', () => { - let producer; + let producer before(() => { - producer = new Kafka.Producer(); - return producer.init().then(() => emailServer.start()); - }); + producer = new Kafka.Producer() + return producer.init().then(() => emailServer.start()) + }) after(function () { - return producer.end(); - }); + return producer.end() + }) describe('Created', () => { it('success', (done) => { - globalDone = done; producer.send({ topic: `${config.KAFKA_TOPIC_PREFIX}email.project.created`, message: { value: JSON.stringify(successTestMessage) } - }); - }); - }); + }) + }) + }) describe('Updated', () => { it('success', (done) => { - globalDone = done; producer.send({ topic: `${config.KAFKA_TOPIC_PREFIX}email.project.updated`, message: { value: JSON.stringify(successTestMessage) } - }); - }); - }); - -}); + }) + }) + }) +})