├── .gitignore ├── .eslintrc ├── lib ├── db │ ├── connection.js │ └── index.js ├── templates │ ├── index.js │ └── lib │ │ ├── welcome-email.js │ │ ├── forgot-password.js │ │ ├── topic-published.js │ │ ├── new-comment.js │ │ └── comment-reply.js ├── utils │ ├── index.js │ ├── graceful-exit.js │ └── listen-once-of.js ├── jobs │ ├── index.js │ └── lib │ │ ├── forgot-password.js │ │ ├── welcome-email.js │ │ ├── topic-published.js │ │ ├── comment-reply.js │ │ └── new-comment.js ├── mailer │ ├── index.js │ └── transporter.js ├── config │ ├── defaults.json │ └── index.js ├── agenda │ └── index.js └── translations │ ├── lib │ ├── fi.json │ ├── pt.json │ ├── nl.json │ ├── it.json │ ├── gl.json │ ├── ca.json │ ├── uk.json │ ├── ru.json │ ├── de.json │ ├── fr.json │ ├── en.json │ └── es.json │ └── index.js ├── LICENSE ├── example.js ├── package.json ├── README.md ├── index.js ├── test └── index.js └── History.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-* 3 | node_modules 4 | npm-debug.log 5 | /sandbox.js 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["democracyos"], 3 | "rules": { 4 | "promise/no-nesting": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/db/connection.js: -------------------------------------------------------------------------------- 1 | const db = require('.') 2 | 3 | module.exports = db.then((db) => { 4 | return db._db 5 | }).catch((err) => { throw err }) 6 | -------------------------------------------------------------------------------- /lib/templates/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var requireAll = require('require-all') 3 | 4 | module.exports = requireAll(path.join(__dirname, '/lib')) 5 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports.emailAddress = function emailAddress (user) { 2 | return { 3 | name: user.lastName ? user.firstName + ' ' + user.lastName : user.firstName, 4 | address: user.email 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/graceful-exit.js: -------------------------------------------------------------------------------- 1 | module.exports = function gracefulExit (callback) { 2 | function graceful() { 3 | callback((err) => { 4 | if (err) { 5 | console.error(err) 6 | process.exit(1) 7 | } 8 | 9 | process.exit(0) 10 | }) 11 | } 12 | 13 | process.on('SIGTERM', graceful) 14 | process.on('SIGINT' , graceful) 15 | } 16 | -------------------------------------------------------------------------------- /lib/jobs/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var requireAll = require('require-all') 3 | 4 | module.exports.init = function init (notifier) { 5 | const jobs = requireAll({ 6 | dirname: path.join(__dirname, '/lib'), 7 | resolve: (job) => typeof job === 'function' && job(notifier) 8 | }) 9 | 10 | return Promise.all(Object.keys(jobs).map((name) => jobs[name])) 11 | } 12 | -------------------------------------------------------------------------------- /lib/mailer/index.js: -------------------------------------------------------------------------------- 1 | const pify = require('pify') 2 | const config = require('../config') 3 | const transporter = require('./transporter') 4 | 5 | const sendMail = pify(transporter.sendMail.bind(transporter)) 6 | 7 | const defaultSender = { 8 | name: config.get('organizationName'), 9 | address: config.get('organizationEmail') 10 | } 11 | 12 | module.exports.send = function send (opts) { 13 | if (!opts.from) opts.from = defaultSender 14 | return sendMail(opts) 15 | } 16 | -------------------------------------------------------------------------------- /lib/mailer/transporter.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | const config = require('../config') 3 | 4 | let opts = config.get('mailer.service') 5 | ? config.get('mailer') 6 | : config.get('nodemailer') 7 | 8 | // Use directTransport when nothing is configured 9 | if (opts && Object.keys(opts).length === 0) { 10 | opts = undefined 11 | } 12 | 13 | const transport = opts ? 14 | nodemailer.createTransport(opts) : 15 | nodemailer.createTransport({}) 16 | 17 | module.exports = transport 18 | -------------------------------------------------------------------------------- /lib/utils/listen-once-of.js: -------------------------------------------------------------------------------- 1 | module.exports = function listenOnceOf (emitter, callbacks) { 2 | const events = Object.keys(callbacks) 3 | const listeners = {} 4 | 5 | const removeListeners = () => { 6 | events.forEach((event) => { 7 | emitter.removeListener(event, listeners[event]) 8 | }) 9 | } 10 | 11 | events.forEach((event) => { 12 | const listener = listeners[event] = (...args) => { 13 | removeListeners() 14 | callbacks[event](...args) 15 | } 16 | 17 | emitter.once(event, listener) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /lib/templates/lib/welcome-email.js: -------------------------------------------------------------------------------- 1 | const html = require('es6-string-html-template').html 2 | const raw = require('es6-string-html-template').raw 3 | const t = require('t-component') 4 | 5 | module.exports = function welcomeEmail (vars, opts) { 6 | t.lang(opts.lang) 7 | const _t = (key) => t(key, vars) 8 | 9 | return html` 10 |
${_t('templates.email.greeting')}
11 |${raw(_t('templates.welcome-email.body'))}
12 |${raw(_t('templates.email.signature'))}
13 |${_t('templates.welcome-email.ps')}
14 | `.toString() 15 | } 16 | -------------------------------------------------------------------------------- /lib/templates/lib/forgot-password.js: -------------------------------------------------------------------------------- 1 | const html = require('es6-string-html-template').html 2 | const raw = require('es6-string-html-template').raw 3 | const t = require('t-component') 4 | 5 | module.exports = function welcomeEmail (vars, opts) { 6 | t.lang(opts.lang) 7 | const _t = (key) => t(key, vars) 8 | 9 | return html` 10 |${_t('templates.email.greeting')}
11 |${raw(_t('templates.forgot-password.body'))}
12 |${raw(_t('templates.email.signature'))}
13 |${_t('templates.forgot-password.ps')}
14 | `.toString() 15 | } 16 | -------------------------------------------------------------------------------- /lib/templates/lib/topic-published.js: -------------------------------------------------------------------------------- 1 | const html = require('es6-string-html-template').html 2 | const raw = require('es6-string-html-template').raw 3 | const t = require('t-component') 4 | 5 | module.exports = function welcomeEmail (vars, opts) { 6 | t.lang(opts.lang) 7 | const _t = (key) => t(key, vars) 8 | 9 | return html` 10 |${_t('templates.email.greeting')}
11 |${_t('templates.topic-published.body')}
12 |${vars.topic}
13 |${raw(_t('templates.topic-published.body2'))}
14 |${raw(_t('templates.email.signature'))}
15 | `.toString() 16 | } 17 | -------------------------------------------------------------------------------- /lib/config/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoUrl": "mongodb://localhost/DemocracyOS-dev", 3 | "collection": "notifierJobs", 4 | "defaultLocale": "en", 5 | "availableLocales": [ 6 | "ca", 7 | "de", 8 | "en", 9 | "es", 10 | "fi", 11 | "fr", 12 | "gl", 13 | "it", 14 | "nl", 15 | "pt", 16 | "ru", 17 | "uk" 18 | ], 19 | "organizationName": "The DemocracyOS team", 20 | "organizationEmail": "noreply@democracyos.org", 21 | "mailer": { 22 | "service": "", 23 | "auth": { 24 | "user": "", 25 | "pass": "" 26 | } 27 | }, 28 | "nodemailer": {} 29 | } 30 | -------------------------------------------------------------------------------- /lib/agenda/index.js: -------------------------------------------------------------------------------- 1 | const Agenda = require('agenda') 2 | const gracefulExit = require('../utils/graceful-exit') 3 | const config = require('../config') 4 | const connection = require('../db/connection') 5 | 6 | module.exports = connection.then((db) => { 7 | return new Promise((resolve, reject) => { 8 | const agenda = new Agenda({ 9 | mongo: db, 10 | db: { collection: config.get('collection') } 11 | }) 12 | 13 | agenda.on('error', reject) 14 | agenda.on('ready', () => resolve(agenda)) 15 | 16 | gracefulExit((done) => { 17 | agenda.stop(done) 18 | }) 19 | }) 20 | }).catch((err) => { throw err }) 21 | -------------------------------------------------------------------------------- /lib/db/index.js: -------------------------------------------------------------------------------- 1 | const monk = require('monk') 2 | const gracefulExit = require('../utils/graceful-exit') 3 | const listenOnceOf = require('../utils/listen-once-of') 4 | const config = require('../config') 5 | 6 | module.exports = monk(config.get('mongoUrl')).then((db) => { 7 | db.catch = db.then = new Promise((resolve, reject) => { 8 | switch (db._state) { 9 | case 'closed': 10 | reject(new Error('Connection closed')) 11 | break 12 | case 'open': 13 | resolve(db) 14 | break 15 | default: 16 | listenOnceOf(db, { 17 | open: resolve, 18 | 'error-opening': reject 19 | }) 20 | } 21 | }) 22 | 23 | gracefulExit((done) => { 24 | db.close(done) 25 | }) 26 | 27 | return db 28 | }).catch((err) => { throw err }) 29 | -------------------------------------------------------------------------------- /lib/config/index.js: -------------------------------------------------------------------------------- 1 | var defaults = require('defaults-deep') 2 | var defaultConfig = require('./defaults.json') 3 | 4 | var config = defaults({}, defaultConfig) 5 | 6 | /** 7 | * Function to set new configuration values 8 | * @method set 9 | * @param {Object} newConfig new values to set on config 10 | */ 11 | module.exports.set = function configSet (newConfig) { 12 | config = defaults({}, newConfig, defaultConfig) 13 | } 14 | 15 | /** 16 | * Get a configuration value 17 | * @method get 18 | * @param {String} key dot notation key, e.g.: 'mailer.service' 19 | * @return {Mixed} current config value 20 | */ 21 | module.exports.get = function configGet (key) { 22 | if (typeof key !== 'string') throw new Error('"key" should be a string') 23 | 24 | return key.split('.').reduce(function (val, k) { 25 | return val[k] 26 | }, config) 27 | } 28 | -------------------------------------------------------------------------------- /lib/jobs/lib/forgot-password.js: -------------------------------------------------------------------------------- 1 | const t = require('t-component') 2 | const utils = require('../../utils') 3 | const templates = require('../../templates') 4 | 5 | const jobName = 'forgot-password' 6 | 7 | module.exports = function forgotPassword (notifier) { 8 | const { db, agenda, mailer } = notifier 9 | const users = db.get('users') 10 | 11 | agenda.define(jobName, { priority: 'high' }, (job, done) => { 12 | const data = job.attrs.data 13 | 14 | users.findOne({ email: data.to }).then((user) => { 15 | if (!user) throw new Error(`User not found for email "${data.to}"`) 16 | 17 | const html = templates[jobName]({ 18 | userName: user.firstName, 19 | resetPasswordUrl: data.resetUrl 20 | }, { 21 | lang: user.locale 22 | }) 23 | 24 | return mailer.send({ 25 | to: utils.emailAddress(user), 26 | subject: t(`templates.${jobName}.subject`), 27 | html 28 | }) 29 | }).then(() => { done() }).catch(done) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /lib/translations/lib/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hei, {userName},", 3 | "templates.email.signature": "Hi! ${userName},
19 |${comment.author.fullName} commented the topic: "${topicTitle}":
21 |${comment.text}
23 |${raw(`Please click here to see it.`)}
25 | `.toString(), 26 | 27 | es: ({ userName, topicTitle, comment, url }) => html` 28 | ${styles} 29 |Hola ${userName},
30 |${comment.author.fullName} comentó en "${topicTitle}":
31 |${comment.text}
33 |${raw(`Por favor, cliquea aquí para verlo.`)}
35 | `.toString() 36 | } 37 | -------------------------------------------------------------------------------- /lib/translations/lib/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hi, {userName},", 3 | "templates.email.signature": "Hi! ${userName},
19 |${reply.author.fullName} replied to a comment on "${topicTitle}".
21 |Original comment by ${comment.author.fullName}:
23 |${comment.text}
24 |Reply by ${reply.author.fullName}:
26 |${reply.text}
27 |${raw(`Please click here to see it.`)}
29 | `.toString(), 30 | 31 | es: ({ userName, topicTitle, reply, comment, url }) => html` 32 | ${styles} 33 |Hola ${userName},
34 |${reply.author.fullName} respondió un comentario en "${topicTitle}".
35 |Comentario original por ${comment.author.fullName}:
36 |${comment.text}
37 |Respuesta por ${reply.author.fullName}:
39 |${reply.text}
40 |${raw(`Por favor, cliquea aquí para verla.`)}
42 | `.toString() 43 | } 44 | -------------------------------------------------------------------------------- /lib/jobs/lib/comment-reply.js: -------------------------------------------------------------------------------- 1 | const monk = require('monk') 2 | const t = require('t-component') 3 | const templates = require('../../templates') 4 | const utils = require('../../utils') 5 | 6 | const jobName = 'comment-reply' 7 | const jobNameForSingleUser = 'comment-reply-single-recipient' 8 | 9 | module.exports = function topicPublished (notifier) { 10 | const { db, agenda, mailer } = notifier 11 | const users = db.get('users') 12 | const comments = db.get('comments') 13 | 14 | agenda.define(jobName, (job, done) => { 15 | const { topic, comment, reply, url } = job.attrs.data 16 | 17 | comments.distinct('replies.author', { 18 | _id: monk.id(comment.id) 19 | }).then((usersToNotify) => { 20 | usersToNotify.push(monk.id(comment.author.id)) 21 | 22 | return users.find({ 23 | $and: [ 24 | { 25 | _id: { $in: usersToNotify }, 26 | 'notifications.replies': true 27 | }, 28 | { _id: { $ne: monk.id(reply.author.id) } } 29 | ] 30 | }).each((user, { pause, resume }) => { 31 | pause() 32 | 33 | agenda.now(jobNameForSingleUser, { 34 | topicTitle: topic.mediaTitle, 35 | comment, 36 | reply, 37 | url, 38 | to: utils.emailAddress(user), 39 | locale: user.locale 40 | }, (err) => { 41 | if (err) return done(err) 42 | resume() 43 | }) 44 | }) 45 | }).then(() => { done() }).catch(done) 46 | }) 47 | 48 | agenda.define(jobNameForSingleUser, (job, done) => { 49 | const { 50 | topicTitle, 51 | comment, 52 | reply, 53 | url, 54 | to, 55 | locale 56 | } = job.attrs.data 57 | 58 | const html = templates[jobName]({ 59 | userName: to.name, 60 | topicTitle, 61 | comment, 62 | reply, 63 | url 64 | }, { 65 | lang: locale 66 | }) 67 | 68 | return mailer.send({ 69 | to, 70 | subject: t(`templates.${jobName}.subject`), 71 | html 72 | }).then(() => { done() }).catch(done) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /lib/jobs/lib/new-comment.js: -------------------------------------------------------------------------------- 1 | const monk = require('monk') 2 | const t = require('t-component') 3 | const templates = require('../../templates') 4 | const utils = require('../../utils') 5 | 6 | const jobName = 'new-comment' 7 | const jobNameForSingleUser = 'new-comment-single-recipient' 8 | 9 | module.exports = function topicPublished (notifier) { 10 | const { db, agenda, mailer } = notifier 11 | const users = db.get('users') 12 | const comments = db.get('comments') 13 | const forums = db.get('forums') 14 | 15 | agenda.define(jobName, (job, done) => { 16 | const { topic, comment, url } = job.attrs.data 17 | forums.findOne({_id: topic.forum}) 18 | .then((forum) => { 19 | const moderators = forum.permissions && forum.permissions 20 | .filter((p) => p.role === 'moderator') 21 | .map((r) => r.user) 22 | if (!moderators || moderators.length === 0) return 23 | 24 | return users.find({ 25 | $and: [ 26 | { _id: { $in: moderators } }, 27 | { _id: { $ne: monk.id(comment.author.id) } } 28 | ] 29 | }).each((user, { pause, resume }) => { 30 | pause() 31 | 32 | agenda.now(jobNameForSingleUser, { 33 | topicTitle: topic.mediaTitle, 34 | comment, 35 | url, 36 | to: utils.emailAddress(user), 37 | locale: user.locale 38 | }, (err) => { 39 | if (err) return done(err) 40 | resume() 41 | }) 42 | }) 43 | }).then(() => { done() }).catch(done) 44 | }) 45 | 46 | agenda.define(jobNameForSingleUser, (job, done) => { 47 | const { 48 | topicTitle, 49 | comment, 50 | url, 51 | to, 52 | locale 53 | } = job.attrs.data 54 | 55 | const html = templates[jobName]({ 56 | userName: to.name, 57 | topicTitle, 58 | comment, 59 | url 60 | }, { 61 | lang: locale 62 | }) 63 | 64 | return mailer.send({ 65 | to, 66 | subject: t(`templates.${jobName}.subject`, { topicTitle }), 67 | html 68 | }).then(() => { done() }).catch(done) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const pify = require('pify') 2 | const config = require('./lib/config') 3 | const templates = require('./lib/templates') 4 | 5 | // Load translations 6 | require('./lib/translations') 7 | 8 | const notifier = module.exports = {} 9 | 10 | /** 11 | * Notifier Init 12 | * Initialize async dependencies, including DB connection, jobs, etc 13 | * 14 | * @method init 15 | * @return {Notifier} 16 | */ 17 | 18 | let initialization = null 19 | 20 | notifier.init = function init () { 21 | if (initialization) return initialization 22 | 23 | initialization = Promise.all([ 24 | require('./lib/db'), 25 | require('./lib/mailer'), 26 | require('./lib/agenda') 27 | ]).then(([db, mailer, agenda]) => { 28 | notifier.db = db 29 | notifier.mailer = mailer 30 | notifier.agenda = agenda 31 | 32 | /** 33 | * Promisified verions of Agenda#every, Agenda#schedule and 34 | * Agenda#now methods 35 | */ 36 | ;['every', 'schedule', 'now'].forEach((method) => { 37 | notifier[method] = pify(agenda[method].bind(agenda)) 38 | }) 39 | 40 | return Promise.resolve(notifier) 41 | }).then((notifier) => { 42 | return require('./lib/jobs').init(notifier) 43 | }).catch((err) => { throw err }) 44 | 45 | return initialization 46 | } 47 | 48 | /** 49 | * Notifier Server Start 50 | * Start processing jobs 51 | * 52 | * @method start 53 | * @return {Notifier} 54 | */ 55 | 56 | notifier.start = function start () { 57 | return notifier.init().then(() => { 58 | notifier.agenda.start() 59 | return Promise.resolve(notifier) 60 | }).catch((err) => { throw err }) 61 | } 62 | 63 | /** 64 | * Expose config, this allows the overriding of any config option. 65 | * @return {Config} 66 | */ 67 | 68 | notifier.config = config 69 | 70 | /** 71 | * Expose templates, this allows the overriding of any template. 72 | * @return {Object} 73 | */ 74 | 75 | notifier.templates = templates 76 | 77 | /** 78 | * Expose db connection using mongojs 79 | * Will be defined after the call of init() 80 | * @return {MongoJS} 81 | */ 82 | 83 | notifier.db = null 84 | 85 | /** 86 | * Expose Agenda instance 87 | * Will be defined after the call of init() 88 | * @return {Agenda} 89 | */ 90 | 91 | notifier.agenda = null 92 | 93 | /** 94 | * Email sender utility 95 | * Will be defined after the call of init() 96 | * @return {Mailer} 97 | */ 98 | 99 | notifier.mailer = null 100 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | const expect = chai.expect 4 | 5 | chai.should() 6 | 7 | const db = 'mongodb://localhost/notifier-test' 8 | 9 | describe('notifier', function () { 10 | it('should be properly initialised with its default values', function (done) { 11 | var notifier = require('../') 12 | notifier({ mongoUrl: db }, function () { 13 | expect(notifier.notify).to.be.a('function') 14 | done() 15 | }) 16 | }) 17 | }) 18 | 19 | describe('jobs', function () { 20 | // TODO: write tests for jobs module 21 | }) 22 | 23 | describe('templates', function () { 24 | // TODO: write tests for templates module 25 | }) 26 | 27 | describe('utils', function () { 28 | describe('.name.format()', function () { 29 | it('should generate full name based on presence of firstName and lastName props', function () { 30 | var name = require('../lib/utils/name').format 31 | 32 | expect(name({ firstName: 'bob' })).to.eql('bob') 33 | expect(name({ firstName: 'bob', lastName: 'builder' })).to.eql('bob builder') 34 | }) 35 | 36 | // unsure if checks are made elsewhere 37 | /* 38 | it("should throw exception on invalid input", function() { 39 | expect(function() { 40 | name({last_name: "builder"}); 41 | }).to.throw(Error); 42 | }) 43 | */ 44 | }) 45 | }) 46 | 47 | describe('translations', function () { 48 | var t = require('../lib/translations').t 49 | t.test = { 50 | 'templates.email.greeting': 'Hi, {USER_NAME},', 51 | 'templates.email.signature': 'The DemocracyOS team.' 52 | } 53 | 54 | t.test2 = { 55 | 'templates.email.greeting': 'Bonjour, {USER_NAME},', 56 | 'templates.email.signature': "L'équipe de DemocracyOS." 57 | } 58 | 59 | it('should return linguistic version of prop', function () { 60 | expect(t('templates.email.signature', 'test')).to.eql('The DemocracyOS team.') 61 | expect(t('templates.email.signature', 'test2')).to.eql("L'équipe de DemocracyOS.") 62 | }) 63 | 64 | it('should return placeholders as-is if no props match', function () { 65 | expect(t('templates.email.greeting', 'test')).to.eql('Hi, {USER_NAME},') 66 | expect(t('templates.email.greeting', 'test2')).to.eql('Bonjour, {USER_NAME},') 67 | 68 | expect(t('templates.email.greeting', { user: 'bobby' }, 'test')).to.eql('Hi, {USER_NAME},') 69 | expect(t('templates.email.greeting', { user: 'bobby' }, 'test2')).to.eql('Bonjour, {USER_NAME},') 70 | }) 71 | 72 | it('should swap placeholders with prop values', function () { 73 | expect(t('templates.email.greeting', { USER_NAME: 'Bob the builder' }, 'test')).to.eql('Hi, Bob the builder,') 74 | expect(t('templates.email.greeting', { USER_NAME: 'Bob the builder' }, 'test2')).to.eql('Bonjour, Bob the builder,') 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.1.2 / 2017-07-27 3 | ================== 4 | 5 | * fix emtpy mailer configuration error 6 | 7 | 2.1.0 / 2017-07-10 8 | ================== 9 | 10 | * Fix replies query 11 | * Add new comment notification 12 | * Expose templates 13 | 14 | 2.0.3 / 2017-07-09 15 | ================== 16 | 17 | * Fix double initialization 18 | 19 | 2.0.2 / 2017-07-09 20 | ================== 21 | 22 | * Fix graceful exit 23 | 24 | 2.0.1 / 2017-07-09 25 | ================== 26 | 27 | * Fix node-graceful for agenda 28 | * Update author comparison 29 | 30 | 2.0.0 / 2017-07-09 31 | ================== 32 | 33 | * Complete notifier refactor 34 | 35 | 1.5.0 / 2017-06-02 36 | ================== 37 | 38 | * Remove timing function with moment dependency 39 | * Remove feeds collection connection 40 | * Remove update-feed, topic-voted, and topic-commented Jobs 41 | * Add eslint-config-democracyos 42 | 43 | 1.4.0 / 2017-05-25 44 | ================== 45 | 46 | * Add jobs.define method and refactor 47 | * Add cache for DB connection 48 | 49 | 1.3.1 / 2017-04-21 50 | ================== 51 | 52 | * Fix topic-published multiple calling to done() function DemocracyOS/democracyos#1393 53 | * Fix error messages Closes #30 54 | 55 | 1.3.0 / 2017-01-26 56 | ================== 57 | 58 | * Update agenda dependency to ~0.9.0 59 | 60 | 1.2.0 / 2017-01-25 61 | ================== 62 | 63 | * Update dependencies 64 | 65 | 1.1.3 / 2016-11-04 66 | ================== 67 | 68 | * fix defaulting to directTransport email sender 69 | * Fix for the ReDOS vulnerability #29 70 | 71 | 1.1.2 / 2016-10-24 72 | ================== 73 | 74 | * Fix notifier crash because of authMechanism 75 | 76 | 1.1.1 / 2016-10-19 77 | ================== 78 | 79 | * Update dependencies 80 | 81 | 1.1.0 / 2016-10-18 82 | ================== 83 | 84 | * Add generic config for nodemailer 85 | * Add availableLocales config option 86 | * Refactor configuration on its own module 87 | * Add unit tests #25 thanks @SebastienDaniel! 88 | 89 | 1.0.1 / 2016-03-02 90 | ================== 91 | 92 | * Correctly fallback missing translations to English 93 | 94 | 0.0.16 / 2016-02-03 95 | =================== 96 | 97 | * Re-add topic-published job 98 | 99 | 0.0.15 / 2015-11-03 100 | =================== 101 | 102 | * Fix locale on comment-reply email 103 | * Added ability to send notifications in the language of the target user 104 | 105 | 0.0.14 / 2015-10-29 106 | ================== 107 | 108 | * Force disable the topic-published action treatment 109 | * update mongojs and authentication method 110 | * update agenda 111 | 112 | 0.0.12 / 2015-10-02 113 | =================== 114 | 115 | * Using democracyos/agenda because npm version is outdated and does not work with MongoLab 116 | 117 | 0.0.10 / 2015-08-25 118 | =================== 119 | 120 | * Replace mailchimp for nodemailer #6 121 | * Fix missing opts reference 122 | * Fix default options & add default sender 123 | 124 | 0.0.9 / 2015-08-06 125 | ================== 126 | 127 | * Remove second parameter from done callback 128 | 129 | 0.0.8 / 2015-08-06 130 | ================== 131 | 132 | * Remove checking for 'done' being defined before calling it 133 | 134 | 0.0.7 / 2015-08-06 135 | ================== 136 | 137 | * Add topic-published job handler 138 | * Add topic voted job handler 139 | * Add topic commented job handler 140 | * Add .eslintrc file 141 | 142 | 0.0.6 / 2015-08-01 143 | ================== 144 | 145 | * Add topic-published event 146 | 147 | 0.0.5 / 2015-07-30 148 | ================== 149 | 150 | * Add comment-reply job 151 | 152 | 0.0.4 / 2015-07-30 153 | ================== 154 | 155 | * Update log usage 156 | * Fix `data` parameter missusage 157 | 158 | 0.0.3 / 2015-07-30 159 | ================== 160 | 161 | * Fix relative require paths 162 | 163 | 0.0.2 / 2015-07-30 164 | ================== 165 | 166 | * Add welcome-email job for events `signup` and `resend-validation` 167 | * Refactor part of events/jobs tier 168 | * Refactor forgot-password semantics to reset-password 169 | * Update debug log labels 170 | 171 | 0.0.1 / 2015-07-29 172 | ================== 173 | 174 | * Notifications engine and support for `forgot-password` event 175 | --------------------------------------------------------------------------------