From a8a1c43f2cdb61f67e78728f476cb58b53e8d0f9 Mon Sep 17 00:00:00 2001 From: vjrj Date: Mon, 5 Mar 2018 09:59:55 +0100 Subject: [PATCH] Fire comments email notifications --- imports/modules/get-email-of-user.js | 19 ++ imports/modules/server/send-email.js | 18 +- imports/startup/common/comments.js | 3 +- imports/startup/server/comments.js | 80 +++++ imports/startup/server/index.js | 2 +- imports/startup/server/migrations.js | 8 +- .../startup/server/notificationsObserver.js | 27 +- .../email-templates/new-fire-comment-en.html | 317 ++++++++++++++++++ .../email-templates/new-fire-comment-en.txt | 8 + .../email-templates/new-fire-comment-es.html | 317 ++++++++++++++++++ .../email-templates/new-fire-comment-es.txt | 8 + private/email-templates/new-fire-en.html | 2 +- private/email-templates/new-fire-en.txt | 2 +- private/email-templates/new-fire-es.html | 2 +- private/email-templates/new-fire-es.txt | 2 +- private/email-templates/reset-password-en.txt | 2 +- private/email-templates/reset-password-es.txt | 2 +- private/email-templates/verify-email-en.txt | 2 +- private/email-templates/verify-email-es.txt | 2 +- private/email-templates/welcome-en.txt | 2 +- private/email-templates/welcome-es.txt | 2 +- public/locales/en/common.json | 4 +- public/locales/es/common.json | 4 +- test/email.test.js | 27 ++ 24 files changed, 820 insertions(+), 42 deletions(-) create mode 100644 imports/modules/get-email-of-user.js create mode 100644 imports/startup/server/comments.js create mode 100644 private/email-templates/new-fire-comment-en.html create mode 100644 private/email-templates/new-fire-comment-en.txt create mode 100644 private/email-templates/new-fire-comment-es.html create mode 100644 private/email-templates/new-fire-comment-es.txt create mode 100644 test/email.test.js diff --git a/imports/modules/get-email-of-user.js b/imports/modules/get-email-of-user.js new file mode 100644 index 0000000..c69262d --- /dev/null +++ b/imports/modules/get-email-of-user.js @@ -0,0 +1,19 @@ +/* eslint-disable import/no-absolute-path */ + +import getOAuthProfile from '/imports/modules/get-oauth-profile'; + +const getEmailOf = (user) => { + const hasPassword = user.services && user.services.password && user.services.password.bcrypt; + + // console.log(`Has password: ${hasPassword}`); + const OAuthProfile = getOAuthProfile({ + password: hasPassword, + profile: user.profile + }, user); + const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first; + const emailAddress = OAuthProfile ? OAuthProfile.email : + user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null; + return { emailAddress, firstName }; +}; + +export default getEmailOf; diff --git a/imports/modules/server/send-email.js b/imports/modules/server/send-email.js index 8d6fc32..8fc4629 100644 --- a/imports/modules/server/send-email.js +++ b/imports/modules/server/send-email.js @@ -1,11 +1,21 @@ +/* eslint-disable import/no-absolute-path */ + import { Meteor } from 'meteor/meteor'; -// import { Email } from 'meteor/email'; +import i18n from 'i18next'; +import { getFileNameOfLang } from '/imports/api/Utility/server/files.js'; +import sendMail from '/imports/startup/server/email'; import getPrivateFile from './get-private-file'; import templateToText from './handlebars-email-to-text'; import templateToHTML from './handlebars-email-to-html'; -import { getFileNameOfLang } from '/imports/api/Utility/server/files.js'; -import sendMail from '/imports/startup/server/email'; -import i18n from 'i18next'; + +export function subjectTruncate(n = 70, useWordBoundary = true) { + // https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript + if (this.length <= n) { + return `${this}`; + } + const subString = this.substr(0, n - 1); + return `${useWordBoundary ? subString.substr(0, subString.lastIndexOf(' ')) : subString}...`; +} const sendEmail = (options, { resolve, reject }) => { try { diff --git a/imports/startup/common/comments.js b/imports/startup/common/comments.js index 728ec88..7b9ab18 100644 --- a/imports/startup/common/comments.js +++ b/imports/startup/common/comments.js @@ -3,7 +3,8 @@ // Client and Server Comments.config({ rating: 'likes-and-dislikes', - allowAnonymous: () => true, + allowAnonymous: () => false, + allowReplies: () => false, // disabled right now: I don't know hot to get referenceId of replies anonymousSalt: 'klasddl3lala0l3lasdlas0ol3lasdlao3lasdoaslaldal3lasdclasdlal3lasdladlaq', publishUserFields: { profile: 1 diff --git a/imports/startup/server/comments.js b/imports/startup/server/comments.js new file mode 100644 index 0000000..b010405 --- /dev/null +++ b/imports/startup/server/comments.js @@ -0,0 +1,80 @@ +/* global Comments */ +/* eslint-disable import/no-absolute-path */ + +import { Meteor } from 'meteor/meteor'; +import i18n from 'i18next'; +import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email'; +import getEmailOf from '/imports/modules/get-email-of-user'; +import '../common/comments'; + +/* import i18n from 'i18next'; +import moment from 'moment'; +import { dateLongFormat } from '/imports/api/Common/dates'; +import Notifications from '/imports/api/Notifications/Notifications'; +// import sendMail from '/imports/startup/server/email'; +// import { hr } from '/imports/startup/server/email'; +import getOAuthProfile from '/imports/modules/get-oauth-profile'; +import image from 'google-maps-image-api-url'; +import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; */ + +Meteor.startup(() => { + // On Server or Client (preferably on Server) + Comments.config({ + onEvent: (name, action, payload) => { + // e.g send a mail + // console.log(`name: ${name}, action: ${action}, payload: ${JSON.stringify(payload)}`); + + // Samples: + + // name: comment, action: add, payload: {"referenceId":"fire-56a9cc5a5586879eb9cc6dd7","content":"asdfasdf asd fasd fas","userId":"tdK4e43ikjDXct7Gj","isAnonymous":false,"createdAt":"2018-03-04T14:53:40.319Z","likes":[],"dislikes":[],"replies":[],"media":{},"status":"approved","starRatings":[],"ratingScore":0,"_id":"mfAe3HL9qNofhiAnh"} + // name: reply, action: add, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"} + // name: reply, action: like, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":["tdK4e43ikjDXct7Gj"],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":1,"_id":"DyHt28rF6rTDnC5fP","ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"} + // name: comment, action: like, payload: {"_id":"mfAe3HL9qNofhiAnh","ratedUserId":"tdK4e43ikjDXct7Gj"} + // name: reply, action: edit, payload: {"_id":"DyHt28rF6rTDnC5fP","replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf dasd fadsfa","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"starRatings":[],"ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"} + // name: reply, action: remove, payload: {"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"} + + // name: (comment|reply) + // action: (add|like|edit|remove) + + if (name === 'comment' && action.match(/add|like|edit/)) { + if (action === 'add') { + // Check for other users that did comments and send an email (uniq users, and not this user) + // console.log(payload.referenceId); + const query = payload.isAnonymous ? + { referenceId: payload.referenceId } : + { referenceId: payload.referenceId, userId: { $ne: payload.userId } }; + Comments.getCollection().rawCollection().distinct('userId', query).then((users) => { + // console.log(users); + const path = payload.referenceId.replace(/fire-/, 'fire/archive/'); + const fireUrl = Meteor.absoluteUrl(path); + // console.log(fireUrl); + Meteor.users.find({ _id: { $in: users } }).forEach((user) => { + const { firstName, emailAddress } = getEmailOf(user); + // console.log(JSON.stringify(user)); + if (emailAddress) { + const emailOpts = { + to: emailAddress, + subject: subjectTruncate.apply(i18n.t('Hay más información sobre un fuego')), + lang: user.lang, + template: 'new-fire-comment', + templateVars: { + applicationName: i18n.t('AppName'), + firstName, + fireUrl + } + }; + sendEmail(emailOpts).catch((error) => { + throw new Meteor.Error('500', `${error}`); + }); + } + }); + }); + } + } + if (name === 'reply' && action.match(/add|like|edit/)) { + // Check for other previous users to this thread and send an email + // console.log('Reply op'); + } + } + }); +}); diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 101d249..6c5be88 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -8,7 +8,7 @@ import './IPGeocoder'; import './migrations'; import './notificationsObserver'; import './facts'; -import '../common/comments'; +import './comments'; import './sitemaps'; import './subsUnion'; import './prerender'; diff --git a/imports/startup/server/migrations.js b/imports/startup/server/migrations.js index 8605bd0..af33302 100644 --- a/imports/startup/server/migrations.js +++ b/imports/startup/server/migrations.js @@ -1,4 +1,4 @@ -/* global Migrations */ +/* global Migrations, Comments */ /* eslint-disable import/no-absolute-path */ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; @@ -157,6 +157,12 @@ Meteor.startup(() => { } }); + Migrations.add({ + version: 11, + up: function noAnonComments() { + Comments.getCollection().remove({ isAnonymous: true }); + } + }); // Set createdAt in users & subs Migrations.migrateTo('latest'); diff --git a/imports/startup/server/notificationsObserver.js b/imports/startup/server/notificationsObserver.js index 68bdeac..85cd531 100644 --- a/imports/startup/server/notificationsObserver.js +++ b/imports/startup/server/notificationsObserver.js @@ -6,24 +6,13 @@ import moment from 'moment'; import { dateLongFormat } from '/imports/api/Common/dates'; import Notifications from '/imports/api/Notifications/Notifications'; // import sendMail from '/imports/startup/server/email'; -import sendEmail from '/imports/modules/server/send-email'; +import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email'; // import { hr } from '/imports/startup/server/email'; -import getOAuthProfile from '/imports/modules/get-oauth-profile'; +import getEmailOf from '/imports/modules/get-email-of-user'; import image from 'google-maps-image-api-url'; import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; Meteor.startup(() => { - // https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript - function truncate(n, useWordBoundary) { - if (this.length <= n) { - return this; - } - const subString = this.substr(0, n - 1); - return `${useWordBoundary ? - subString.substr(0, subString.lastIndexOf(' ')) : - subString}...`; - } - // https://www.npmjs.com/package/google-maps-image-api-url // https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email // https://developers.google.com/maps/documentation/static-maps/intro @@ -49,16 +38,8 @@ Meteor.startup(() => { function process(notif) { if (notif.type === 'web' && !notif.emailNotified) { const user = Meteor.users.findOne({ _id: notif.userId }); - const hasPassword = user.services && user.services.password && user.services.password.bcrypt; + const { firstName, emailAddress } = getEmailOf(user); - // console.log(`Has password: ${hasPassword}`); - const OAuthProfile = getOAuthProfile({ - password: hasPassword, - profile: user.profile - }, user); - const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first; - const emailAddress = OAuthProfile ? OAuthProfile.email : - user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null; if (emailAddress) { const img = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); // const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); @@ -77,7 +58,7 @@ Meteor.startup(() => { to: emailAddress, // userName: firstName, // sendAt: new Date(), - subject: truncate.apply(message, [50, true]), + subject: subjectTruncate.apply(message), // text: `${message}\n\n${fireTextUrl}\n\n`, // template: '

{{appName}}

{{{html}}}', lang: user.lang, diff --git a/private/email-templates/new-fire-comment-en.html b/private/email-templates/new-fire-comment-en.html new file mode 100644 index 0000000..9978894 --- /dev/null +++ b/private/email-templates/new-fire-comment-en.html @@ -0,0 +1,317 @@ + + + + + +[{{applicationName}}] There is more information about a fire + + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + + + + + + + +
+ Hey, {{firstName}}! +
+ There is more information about a fire to which you made comments: +
+ +
+ Thanks!
+ {{applicationName}} Team +
+
+
+
+ + + diff --git a/private/email-templates/new-fire-comment-en.txt b/private/email-templates/new-fire-comment-en.txt new file mode 100644 index 0000000..459f81a --- /dev/null +++ b/private/email-templates/new-fire-comment-en.txt @@ -0,0 +1,8 @@ +Hey, {{firstName}}! + +There is more information about a fire to which you made comments: + +{{fireUrl}} + +Thanks! +{{applicationName}} Team diff --git a/private/email-templates/new-fire-comment-es.html b/private/email-templates/new-fire-comment-es.html new file mode 100644 index 0000000..389b637 --- /dev/null +++ b/private/email-templates/new-fire-comment-es.html @@ -0,0 +1,317 @@ + + + + + +[{{applicationName}}] Hay más información sobre un fuego + + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + + + + + + + +
+ Hola, {{firstName}}! +
+ Hay más información sobre un fuego al que hiciste comentarios: +
+ +
+ Gracias!
+ El equipo de {{applicationName}} +
+
+
+
+ + + diff --git a/private/email-templates/new-fire-comment-es.txt b/private/email-templates/new-fire-comment-es.txt new file mode 100644 index 0000000..8028274 --- /dev/null +++ b/private/email-templates/new-fire-comment-es.txt @@ -0,0 +1,8 @@ +Hola, {{firstName}}! + +Hay más información sobre un fuego al que hiciste comentarios: + +{{fireUrl}} + +Gracias! +El equipo de {{applicationName}} diff --git a/private/email-templates/new-fire-en.html b/private/email-templates/new-fire-en.html index b0f7fcb..1ca0246 100644 --- a/private/email-templates/new-fire-en.html +++ b/private/email-templates/new-fire-en.html @@ -274,7 +274,7 @@
- +
diff --git a/private/email-templates/new-fire-en.txt b/private/email-templates/new-fire-en.txt index 1b00d23..d8ec41f 100644 --- a/private/email-templates/new-fire-en.txt +++ b/private/email-templates/new-fire-en.txt @@ -2,7 +2,7 @@ Hey, {{firstName}}! {{message}} -[More information about this fire]({{fireUrl}}) +More information about this fire: {{fireUrl}} Isn't it a forest fire? In the previous link you can tell us what kind of fire it is and help us improve our notifications. diff --git a/private/email-templates/new-fire-es.html b/private/email-templates/new-fire-es.html index e254a8a..6129aeb 100644 --- a/private/email-templates/new-fire-es.html +++ b/private/email-templates/new-fire-es.html @@ -274,7 +274,7 @@
- +
diff --git a/private/email-templates/new-fire-es.txt b/private/email-templates/new-fire-es.txt index 36bb3b9..3e0568f 100644 --- a/private/email-templates/new-fire-es.txt +++ b/private/email-templates/new-fire-es.txt @@ -2,7 +2,7 @@ Hola, {{firstName}}! {{message}} -[Más información sobre este fuego]({{fireUrl}}) +Más información sobre este fuego: {{fireUrl}} ¿No es un fuego forestal? En el enlace anterior puedes indicarnos de que tipo de fuego se trata y ayudarnos así a mejorar nuestras notificaciones. diff --git a/private/email-templates/reset-password-en.txt b/private/email-templates/reset-password-en.txt index 5519476..ba4333d 100644 --- a/private/email-templates/reset-password-en.txt +++ b/private/email-templates/reset-password-en.txt @@ -4,7 +4,7 @@ A password reset has been requested for the account related to this email addres To reset the password, visit the following link: -[Reset Password]({{resetUrl}}) +{{resetUrl}} Thanks! {{applicationName}} Team diff --git a/private/email-templates/reset-password-es.txt b/private/email-templates/reset-password-es.txt index 9996d66..ef9b3c3 100644 --- a/private/email-templates/reset-password-es.txt +++ b/private/email-templates/reset-password-es.txt @@ -4,7 +4,7 @@ Se ha solicitado un reseteo de contraseña para la cuenta asociada a este correo Para resetear la contraseña, visita el siguiente enlace: -[Resetea la contraseña]({{resetUrl}}) +{{resetUrl}} Gracias! El equipo de {{applicationName}} diff --git a/private/email-templates/verify-email-en.txt b/private/email-templates/verify-email-en.txt index cd22d81..cc228c7 100644 --- a/private/email-templates/verify-email-en.txt +++ b/private/email-templates/verify-email-en.txt @@ -2,7 +2,7 @@ Hey, {{firstName}}! Can you do us a favor and verify your email address? -[Verify Email Address]({{verifyUrl}}) +Verify Email Address: {{verifyUrl}} Thanks! {{applicationName}} Team diff --git a/private/email-templates/verify-email-es.txt b/private/email-templates/verify-email-es.txt index 361f14b..b5ec1ae 100644 --- a/private/email-templates/verify-email-es.txt +++ b/private/email-templates/verify-email-es.txt @@ -2,7 +2,7 @@ Hola, {{firstName}}! ¿Nos puedes hacer un favor y verificar tu correo electrónico? -[Verificar correo electrónico]({{verifyUrl}}) +Verificar correo electrónico: {{verifyUrl}} Gracias! El equipo de {{applicationName}} diff --git a/private/email-templates/welcome-en.txt b/private/email-templates/welcome-en.txt index 6dcb0d9..47bfd8e 100644 --- a/private/email-templates/welcome-en.txt +++ b/private/email-templates/welcome-en.txt @@ -4,7 +4,7 @@ Welcome to {{applicationName}}. We thank you for collaborating with us. Ready to get started? -[View Your Subscriptions to Fires]({{welcomeUrl}}) +View Your Subscriptions to Fires: {{welcomeUrl}} Thanks, {{applicationName}} Team diff --git a/private/email-templates/welcome-es.txt b/private/email-templates/welcome-es.txt index 99dabed..1db2fca 100644 --- a/private/email-templates/welcome-es.txt +++ b/private/email-templates/welcome-es.txt @@ -4,7 +4,7 @@ Bienvenid@ a {{applicationName}}. Te agradecemos que colabores con nosotr@s. ¿Preparada para empezar? -[Ver tus subscripciones a fuegos]({{welcomeUrl}}) +Ver tus subscripciones a fuegos: {{welcomeUrl}} Gracias, El equipo de {{applicationName}} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 9a7d412..08f0210 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -243,5 +243,7 @@ "Cancelar": "Cancel", "Nota: Las nubes pueden entorpecer la detección de fuegos activos.": - "Note: Cloud cover might obscure active fire detections." + "Note: Cloud cover might obscure active fire detections.", + "Hay más información sobre un fuego": + "There is more information about a fire" } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index a2f8abc..e39d218 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -328,5 +328,7 @@ "Cancelar": "Cancelar", "Nota: Las nubes pueden entorpecer la detección de fuegos activos.": - "Nota: Las nubes pueden entorpecer la detección de fuegos activos." + "Nota: Las nubes pueden entorpecer la detección de fuegos activos.", + "Hay más información sobre un fuego": + "Hay más información sobre un fuego" } diff --git a/test/email.test.js b/test/email.test.js new file mode 100644 index 0000000..0377368 --- /dev/null +++ b/test/email.test.js @@ -0,0 +1,27 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ +/* eslint-disable import/no-absolute-path */ + +import { chai } from 'meteor/practicalmeteor:chai'; +import { subjectTruncate } from '/imports/modules/server/send-email'; + +describe('check email utilities', () => { + it('small words', async () => { + chai.expect('something').equal(subjectTruncate.apply('something', [50, true])); + }); + + it('big sentences', async () => { + chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.') + .equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sed.')); + }); + + it('big sentences space break', async () => { + chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi...') + .equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.')); + }); + + it('big sentences no break', async () => { + chai.expect('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis...') + .equal(subjectTruncate.apply('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis cras amet.', [70, false])); + }); +});