├── .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": " DemocracyOS tiimi.", 4 | 5 | "templates.welcome-email.subject": "Tervetuloa DemocracyOS!", 6 | "templates.welcome-email.body": "Ole hyvä klikkaa tästä vahvistaaksesi email-osoitteesi.", 7 | "templates.welcome-email.ps": "Jos et rekisteröitynyt, älä huomioi tätä viestiä.", 8 | 9 | "templates.forgot-password.subject": "Vaaditaan salasanan päivitys", 10 | "templates.forgot-password.body": "Ole hyvä klikkaa tästä vaihtaaksesi salasanasi", 11 | "templates.forgot-password.ps": "Jos et pyytänyt salasanan uusintaa, älä huomioi tätä viestiä.", 12 | 13 | "templates.topic-published.subject": "Uusi laki julkaistiin", 14 | "templates.topic-published.body": "Uusi laki julkaistiin:", 15 | "templates.topic-published.body2": "Ole hyvä ja klikkaa tästä nähdä sen" 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Olá, {userName},", 3 | "templates.email.signature": " Time - DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Bem-vindo(a) ao DemocracyOS!", 6 | "templates.welcome-email.body": "Por favor clique aqui para validar seu endereço de e-mail.", 7 | "templates.welcome-email.ps": "Se você não se cadastrou, por favor ignore este e-mail.", 8 | 9 | "templates.forgot-password.subject": "Configurar nova senha", 10 | "templates.forgot-password.body": "Por favor, clique aqui para configurar nova senha.", 11 | "templates.forgot-password.ps": "Se você não solicitou criar uma nova senha, por favor ignore este e-mail", 12 | 13 | "templates.topic-published.subject": "Nova lei publicada", 14 | "templates.topic-published.body": "A nova lei foi publicada:", 15 | "templates.topic-published.body2": "Por favor, clique aqui para vê-la." 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hoi, {userName},", 3 | "templates.email.signature": " De DemocracyOS groep.", 4 | 5 | "templates.welcome-email.subject": "Welkom bij DemocracyOS!", 6 | "templates.welcome-email.body": "Klik hier om je e-mail adres te bevestigen.", 7 | "templates.welcome-email.ps": "Als je je niet hebt aangemeld, kun je deze e-mail negeren.", 8 | 9 | "templates.forgot-password.subject": "Wachtwoord herstellen aangevraagd", 10 | "templates.forgot-password.body": "Klik hier om je wachtwoord opnieuw in te stellen.", 11 | "templates.forgot-password.ps": "Als je niet gevraagd hebt om je wachtwoord opnieuw in te stellen, kun je deze e-mail negeren.", 12 | 13 | "templates.topic-published.subject": "Nieuwe wet gepubliceerd", 14 | "templates.topic-published.body": "Een nieuwe wet werd gepubliceerd:", 15 | "templates.topic-published.body2": "Klik hier om het te zien." 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Ciao, {userName},", 3 | "templates.email.signature": " La squadra di DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Benvenuto a DemocracyOS!", 6 | "templates.welcome-email.body": "Per favore clicca qui per convalidare l'email.", 7 | "templates.welcome-email.ps": "P.S.: se non hai firmato in Democracyos preghiamo di ignorare questa email.", 8 | 9 | "templates.forgot-password.subject": "Reimpostare la password", 10 | "templates.forgot-password.body": "Per favore clicca qui per reimpostare la password", 11 | "templates.forgot-password.ps": "P.S.: se non hai richiesto di reimpostare la password, ignorare questa email", 12 | 13 | "templates.topic-published.subject": "Nuova legge pubblicato", 14 | "templates.topic-published.body": "Una nuova legge è stata pubblicata:", 15 | "templates.topic-published.body2": "Si prega di cliccare qui per vederlo" 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/gl.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Ola, {userName}", 3 | "templates.email.signature": " O equipo de DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Benvinda a DemocracyOS!", 6 | "templates.welcome-email.body": "Por favor fai click para validar o teu enderezo de correo electrónico.", 7 | "templates.welcome-email.ps": "P.D.: se non creaches unha conta, por favor ignora esta mensaxe.", 8 | 9 | "templates.forgot-password.subject": "Recuperar contrasinal", 10 | "templates.forgot-password.body": "Por favor fai click para reestablecer o teu contrasinal", 11 | "templates.forgot-password.ps": "P.D.: se non solicitaches recuperar o teu contrasinal, por favor ignora esta mensaxe", 12 | 13 | "templates.topic-published.subject": "Nova proposta publicada", 14 | "templates.topic-published.body": "Unha nova proposta foi publicada:", 15 | "templates.topic-published.body2": "Por favor fai click para vela." 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hola, {userName}.", 3 | "templates.email.signature": " L'equip de DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Benvinguts/des a DemocracyOS!", 6 | "templates.welcome-email.body": "Si us plau cliqui aquí per validar la teva adreça de correu electrònic.", 7 | "templates.welcome-email.ps": "P.D.: Si no has creat un compte a DemocracyOS, si us plau ignora aquest correu.", 8 | 9 | "templates.forgot-password.subject": "Generar una nova contrasenya", 10 | "templates.forgot-password.body": "Si us plau, cliqui aquí per generar la teva contrasenya", 11 | "templates.forgot-password.ps": "P.D.: Si no has generat la teva contrasenya, si us plau ignora aquest correu", 12 | 13 | "templates.topic-published.subject": "Nova llei publicada", 14 | "templates.topic-published.body": "Una nova llei va ser publicada:", 15 | "templates.topic-published.body2": "Si us plau, cliqui aquí per veure-ho" 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Добрий день, {userName},", 3 | "templates.email.signature": " Адміністрація DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Вітаємо!", 6 | "templates.welcome-email.body": "Будь ласка натисніть сюди щоб підтвердити адресу Вашої електронної пошти.", 7 | "templates.welcome-email.ps": "PS: Якщо Ви не реєструвалися, будь ласка не звертайте уваги на це повідомлення.", 8 | 9 | "templates.forgot-password.subject": "Зміна пароля", 10 | "templates.forgot-password.body": "Будь ласка натисніть сюди щоб змінити Ваш пароль", 11 | "templates.forgot-password.ps": "PS: якщо Ви не намагалися змінити Ваш пароль, будь ласка не звертайте уваги на це повідомлення.", 12 | 13 | "templates.topic-published.subject": "Опубліковано новий закон", 14 | "templates.topic-published.body": "Новий закон був опублікований:", 15 | "templates.topic-published.body2": "Будь ласка натисніть сюди щоб побачити його." 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Здравствуйте, {userName},", 3 | "templates.email.signature": " Администрация DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Добро пожаловать!", 6 | "templates.welcome-email.body": "Пожалуйста нажмите сюда чтобы подтвердить адрес Вашей электронной почты.", 7 | "templates.welcome-email.ps": "PS: Если Вы не регистрировались, пожалуйста не обращайте внимания на это сообщение.", 8 | 9 | "templates.forgot-password.subject": "Изменение пароля", 10 | "templates.forgot-password.body": "Пожалуйста нажмите сюда чтобы изменить Ваш пароль", 11 | "templates.forgot-password.ps": "PS: если Вы не пытались изменить Ваш пароль, пожалуйста не обращайте внимания на это сообщение.", 12 | 13 | "templates.topic-published.subject": "Опубликован новый закон", 14 | "templates.topic-published.body": "Новый закон был опубликован:", 15 | "templates.topic-published.body2": "Пожалуйста нажмите сюда чтобы увидеть его." 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 DemocracyOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/translations/lib/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hallo, {userName},", 3 | "templates.email.signature": " Das DemocracyOS-Team.", 4 | 5 | "templates.welcome-email.subject": "Willkommen bei DemocracyOS!", 6 | "templates.welcome-email.body": "Bitte hier klicken, um deine E-Mail-Adresse zu bestätigen.", 7 | "templates.welcome-email.ps": "Bitte diese E-Mail ignorieren, falls du dich nicht bei DemocracyOS angemeldet hast.", 8 | 9 | "templates.forgot-password.subject": "Zurücksetzen des Passworts wurde angefordert", 10 | "templates.forgot-password.body": "Bitte hier klicken, um das Passwort zurückzusetzen.", 11 | "templates.forgot-password.ps": "Bitte diese E-Mail ignorieren, falls kein Zurücksetzen des Passwortes angefordert wurde.", 12 | 13 | "templates.topic-published.subject": "Neues Gesetz veröffentlicht", 14 | "templates.topic-published.body": "Ein neues Gesetz wurde veröffentlicht:", 15 | "templates.topic-published.body2": "Bitte hier klicken, um es zu sehen." 16 | } 17 | -------------------------------------------------------------------------------- /lib/translations/lib/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Salut, {userName},", 3 | "templates.email.signature": " L' équipe de DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "Bienvenue à DemocracyOS!", 6 | "templates.welcome-email.body": "Cliquez s' il vous plaît sur ici pour valider votre adresse de courriel.", 7 | "templates.welcome-email.ps": "Si vous n' avez pas creé un compte sur DemocracyOS, ignorez s' il vous plaît ce courriel.", 8 | 9 | "templates.forgot-password.subject": "DemocracyOS - Rétablir mot de passe", 10 | "templates.forgot-password.body": "Cliquez s' il vous plaît sur ici pour rétablir ton mot de passe", 11 | "templates.forgot-password.ps": "Si vous n' avez pas demandé le rétablissement de votre mot de passe, ignorez s' il vous plaît ce courriel", 12 | 13 | "templates.topic-published.subject": "La nouvelle loi publiée", 14 | "templates.topic-published.body": "Une nouvelle loi a été publiée:", 15 | "templates.topic-published.body2": "Cliquez s'il vous plaît sur ici pour voir" 16 | } 17 | -------------------------------------------------------------------------------- /lib/jobs/lib/welcome-email.js: -------------------------------------------------------------------------------- 1 | const t = require('t-component') 2 | const utils = require('../../utils') 3 | const templates = require('../../templates') 4 | 5 | const jobName = 'welcome-email' 6 | 7 | module.exports = function welcomeEmail (notifier) { 8 | const { db, agenda, mailer } = notifier 9 | const users = db.get('users') 10 | 11 | agenda.define(jobName, { priority: 'high' }, welcomeEmailJob) 12 | agenda.define('signup', { priority: 'high' }, welcomeEmailJob) 13 | agenda.define('resend-validation', { priority: 'high' }, welcomeEmailJob) 14 | 15 | function welcomeEmailJob (job, done) { 16 | const data = job.attrs.data 17 | 18 | users.findOne({ email: data.to }).then((user) => { 19 | if (!user) throw new Error(`User not found for email "${data.to}"`) 20 | 21 | const html = templates[jobName]({ 22 | userName: user.firstName, 23 | validateUrl: data.validateUrl 24 | }, { 25 | lang: user.locale 26 | }) 27 | 28 | return mailer.send({ 29 | to: utils.emailAddress(user), 30 | subject: t(`templates.${jobName}.subject`), 31 | html 32 | }) 33 | }).then(() => { done() }).catch(done) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/templates/lib/new-comment.js: -------------------------------------------------------------------------------- 1 | const html = require('es6-string-html-template').html 2 | const raw = require('es6-string-html-template').raw 3 | 4 | module.exports = function welcomeEmail (vars, { lang }) { 5 | const t = translations[lang] || translations.en 6 | return t(vars) 7 | } 8 | 9 | const styles = raw(` 10 | 13 | `) 14 | 15 | const translations = module.exports.translations = { 16 | en: ({ userName, topicTitle, comment, url }) => html` 17 | ${styles} 18 |

Hi! ${userName},

19 |
20 |

${comment.author.fullName} commented the topic: "${topicTitle}":

21 |
22 |

${comment.text}

23 |
24 |

${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 |
32 |

${comment.text}

33 |
34 |

${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": " The DemocracyOS team.", 4 | 5 | "templates.welcome-email.subject": "Welcome to DemocracyOS!", 6 | "templates.welcome-email.body": "Please click here to validate your email address.", 7 | "templates.welcome-email.ps": "PS: if you didn't sign up, please ignore this email.", 8 | 9 | "templates.forgot-password.subject": "Password reset requested", 10 | "templates.forgot-password.body": "Please click here to reset your password", 11 | "templates.forgot-password.ps": "PS: if you haven't requested to reset your password, please ignore this email", 12 | 13 | "templates.comment-reply.subject": "Someone replied to your argument", 14 | "templates.new-comment.subject": "New comment on \"{topicTitle}\"", 15 | 16 | "templates.topic-published.subject": "New topic published", 17 | "templates.topic-published.body": "A new topic has been published:", 18 | "templates.topic-published.body2": "Please click here to see it." 19 | } 20 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-process-exit */ 2 | 3 | const config = require('./lib/config') 4 | const notifier = require('.') 5 | 6 | config.set({ 7 | "mongoUrl": 'mongodb://localhost/DemocracyOS-dev', 8 | "defaultLocale": "es", 9 | "mailer": { 10 | "service": "gmail", // can be other provider 11 | "auth": { 12 | "user": "my-send-email@gmail.com", 13 | "pass": "dont-commit-me!" 14 | } 15 | } 16 | }) 17 | 18 | notifier.start() 19 | .then(() => { 20 | console.log('Executing job welcome-email') 21 | 22 | return notifier.now('welcome-email', { 23 | to: 'test-to-mail@whatever.com', 24 | validateUrl: 'https://app.democracyos.org' 25 | }) 26 | }) 27 | .then(() => { 28 | return new Promise((resolve, reject) => { 29 | notifier.agenda.once('success:welcome-email', (job) => { 30 | resolve(job) 31 | }) 32 | 33 | notifier.agenda.once('fail:welcome-email', (err, job) => { 34 | reject(err) 35 | }) 36 | }) 37 | }) 38 | .then((job) => { 39 | console.log('User notified!', job.attrs) 40 | process.exit(0) 41 | }) 42 | .catch((err) => { 43 | console.error('Error!', err) 44 | process.exit(1) 45 | }) 46 | -------------------------------------------------------------------------------- /lib/translations/lib/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates.email.greeting": "Hola, {userName}", 3 | "templates.email.signature": " El equipo de DemocracyOS.", 4 | 5 | "templates.welcome-email.subject": "¡Bienvenid@ a DemocracyOS!", 6 | "templates.welcome-email.body": "Por favor cliquea aquí para validar tu dirección de correo electrónico.", 7 | "templates.welcome-email.ps": "P.D.: si no creaste una cuenta, por favor ignora este correo.", 8 | 9 | "templates.forgot-password.subject": "Reestablecer contraseña", 10 | "templates.forgot-password.body": "Por favor cliquea aquí para reestablecer tu contraseña", 11 | "templates.forgot-password.ps": "P.D.: si no solicitaste reestablecer tu contraseña, por favor ignora este correo", 12 | 13 | "templates.comment-reply.subject": "Alguien respondió a tu argumento", 14 | "templates.new-comment.subject": "Un comentario en \"{topicTitle}\"", 15 | 16 | "templates.topic-published.subject": "Nuevo tema publicado", 17 | "templates.topic-published.body": "Un nuevo tema fue publicado:", 18 | "templates.topic-published.body2": "Por favor cliquea aquí para verlo." 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "democracyos-notifier", 3 | "version": "2.1.4", 4 | "description": "Notifications engine with job queuing and scheduling backed by MongoDB.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/DemocracyOS/notifier.git" 12 | }, 13 | "keywords": [ 14 | "notifier", 15 | "democracyos", 16 | "notifiations", 17 | "email" 18 | ], 19 | "author": "Democracia en Red (http://democraciaenred.org/)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/DemocracyOS/notifier/issues" 23 | }, 24 | "homepage": "https://github.com/DemocracyOS/notifier", 25 | "dependencies": { 26 | "agenda": "~0.9.0", 27 | "debug": "~2.6.0", 28 | "defaults-deep": "0.2.4", 29 | "es6-string-html-template": "~1.0.2", 30 | "mongodb": "~2.2.28", 31 | "monk": "~6.0.0", 32 | "nodemailer": "~4.0.1", 33 | "pify": "~3.0.0", 34 | "require-all": "~2.2.0", 35 | "t-component": "1.0.0" 36 | }, 37 | "devDependencies": { 38 | "chai": "~4.0.2", 39 | "eslint": "~3.19.0", 40 | "eslint-config-democracyos": "~1.2.1", 41 | "mocha": "~3.4.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/translations/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var defaults = require('defaults-deep') 4 | var t = require('t-component') 5 | var config = require('../config') 6 | 7 | /** 8 | * Generate an Array with all the translated localizations 9 | */ 10 | var translated = fs.readdirSync(path.join(__dirname, 'lib')).map(function (p) { 11 | return p.replace('.json', '') 12 | }) 13 | 14 | var defaultTranslations = require('./lib/en') 15 | 16 | /** 17 | * Load localization dictionaries to translation application 18 | */ 19 | translated.forEach(function (locale) { 20 | var translation = require('./lib/' + locale) 21 | defaults(translation, defaultTranslations) 22 | t[locale] = translation 23 | }) 24 | 25 | t.lang = (function () { 26 | var original = t.lang 27 | 28 | function getLangCode (code) { 29 | if (!code) return config.get('defaultLocale') 30 | 31 | var isAvailable = config.get('availableLocales').indexOf(code) !== -1 32 | var isTranslated = translated.indexOf(code) !== -1 33 | 34 | if (isAvailable && isTranslated) { 35 | return code 36 | } else { 37 | return config.get('defaultLocale') 38 | } 39 | } 40 | 41 | return function lang (code) { 42 | code = getLangCode(code) 43 | original(code) 44 | return code 45 | } 46 | })() 47 | 48 | module.exports.t = t 49 | -------------------------------------------------------------------------------- /lib/jobs/lib/topic-published.js: -------------------------------------------------------------------------------- 1 | const t = require('t-component') 2 | const templates = require('../../templates') 3 | const utils = require('../../utils') 4 | 5 | const jobName = 'topic-published' 6 | const jobNameForSingleUser = 'topic-published-single-recipient' 7 | 8 | module.exports = function topicPublished (notifier) { 9 | const { db, agenda, mailer } = notifier 10 | const users = db.get('users') 11 | 12 | agenda.define(jobName, (job, done) => { 13 | const { topic, url } = job.attrs.data 14 | 15 | users.find({ 'notifications.new-topic': true }).each((user) => { 16 | return new Promise((resolve, reject) => { 17 | agenda.now(jobNameForSingleUser, { 18 | topic, 19 | url, 20 | to: utils.emailAddress(user), 21 | locale: user.locale 22 | }, (err) => { 23 | if (err) return reject(err) 24 | resolve() 25 | }) 26 | }) 27 | }).then(() => { done() }).catch(done) 28 | }) 29 | 30 | agenda.define(jobNameForSingleUser, (job, done) => { 31 | const { to, topic, url, locale } = job.attrs.data 32 | 33 | const html = templates[jobName]({ 34 | userName: to.name, 35 | topic: topic.mediaTitle, 36 | url 37 | }, { 38 | lang: locale 39 | }) 40 | 41 | return mailer.send({ 42 | to, 43 | subject: t(`templates.${jobName}.subject`), 44 | html 45 | }).then(() => { done() }).catch(done) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DemocracyOS Notifier 2 | Embeddable notifications engine that relies on MongoDB for job queuing and scheduling. Powered by rschmukler/agenda 3 | 4 | ## Mailer Configuration 5 | 6 | Uses [NodeMailer](https://www.npmjs.com/package/nodemailer) package for email handling. Available services are the ones listed on the [nodemailer-wellknown](https://github.com/andris9/nodemailer-wellknown#supported-services) repo. 7 | 8 | ### SendGrid Example 9 | 10 | ```javascript 11 | var notifier = require('democracyos-notifier')({ 12 | mailer: { 13 | service: 'sendgrid', 14 | auth: { 15 | user: 'fake-sendgrid-user!@sendgrid.com', 16 | pass: 'fake-sendgrid-pass' 17 | } 18 | } 19 | }) 20 | ``` 21 | 22 | ### Gmail Example 23 | 24 | ```javascript 25 | var notifier = require('democracyos-notifier')({ 26 | mailer: { 27 | service: 'gmail', 28 | auth: { 29 | user: 'fake-gmail-user!@gmail.com', 30 | pass: 'fake-gmail-pass' 31 | } 32 | } 33 | }) 34 | ``` 35 | 36 | ### Direct Transport Example 37 | 38 | Not recommended for `production`. Using direct transport is not reliable as outgoing port 25 used is often blocked by default. Additionally mail sent from dynamic addresses is often flagged as spam. You should really consider using a SMTP provider. 39 | 40 | ```javascript 41 | var notifier = require('democracyos-notifier')() 42 | ``` 43 | 44 | ### Testing the code 45 | First install packages with `npm install` 46 | 47 | Then modify [`example.js`](./example.js) from and to mails. 48 | 49 | Test running `node example.js` -------------------------------------------------------------------------------- /lib/templates/lib/comment-reply.js: -------------------------------------------------------------------------------- 1 | const html = require('es6-string-html-template').html 2 | const raw = require('es6-string-html-template').raw 3 | 4 | module.exports = function welcomeEmail (vars, { lang }) { 5 | const t = translations[lang] || translations.en 6 | return t(vars) 7 | } 8 | 9 | const styles = raw(` 10 | 13 | `) 14 | 15 | const translations = module.exports.translations = { 16 | en: ({ userName, topicTitle, reply, comment, url }) => html` 17 | ${styles} 18 |

Hi! ${userName},

19 |
20 |

${reply.author.fullName} replied to a comment on "${topicTitle}".

21 |
22 |

Original comment by ${comment.author.fullName}:

23 |

${comment.text}

24 |
25 |

Reply by ${reply.author.fullName}:

26 |

${reply.text}

27 |
28 |

${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 |
38 |

Respuesta por ${reply.author.fullName}:

39 |

${reply.text}

40 |
41 |

${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 | --------------------------------------------------------------------------------