From f20643af3b69a8f8972a979738a420f81c9ffc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 10 Aug 2021 10:56:12 +0200 Subject: [PATCH 01/15] Investigate the graph-ql middleware --- backend/src/middleware/email/emailMiddleware.js | 5 +++++ .../middleware/notifications/notificationsMiddleware.js | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/email/emailMiddleware.js index 571b733d5..de526dd6e 100644 --- a/backend/src/middleware/email/emailMiddleware.js +++ b/backend/src/middleware/email/emailMiddleware.js @@ -76,5 +76,10 @@ export default { AddEmailAddress: sendEmailVerificationMail, requestPasswordReset: sendPasswordResetMail, Signup: sendSignupMail, + // Wolle + // CreatePost: handleContentDataOfPost, + // UpdatePost: handleContentDataOfPost, + // CreateComment: handleContentDataOfComment, + // UpdateComment: handleContentDataOfComment, }, } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index c76b9ca0e..60694dc56 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -6,11 +6,16 @@ const publishNotifications = async (...promises) => { const notifications = await Promise.all(promises) notifications .flat() - .forEach((notificationAdded) => pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })) + .forEach((notificationAdded) => { + pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) + // Wolle + // XXX send e-mails + }) } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const idsOfUsers = extractMentionedUsers(args.content) + // Wolle console.log(context) const post = await resolve(root, args, context, resolveInfo) if (post) { await publishNotifications( From 5fe07c113359f7e25db933455761484820f5d4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 10 Aug 2021 16:13:51 +0200 Subject: [PATCH 02/15] Implement e-mail notification, first step --- .../src/middleware/email/emailMiddleware.js | 5 -- .../notifications/notificationsMiddleware.js | 62 ++++++++++++++++--- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/email/emailMiddleware.js index de526dd6e..571b733d5 100644 --- a/backend/src/middleware/email/emailMiddleware.js +++ b/backend/src/middleware/email/emailMiddleware.js @@ -76,10 +76,5 @@ export default { AddEmailAddress: sendEmailVerificationMail, requestPasswordReset: sendPasswordResetMail, Signup: sendSignupMail, - // Wolle - // CreatePost: handleContentDataOfPost, - // UpdatePost: handleContentDataOfPost, - // CreateComment: handleContentDataOfComment, - // UpdateComment: handleContentDataOfComment, }, } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 60694dc56..d354a7dcb 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -2,25 +2,63 @@ import extractMentionedUsers from './mentions/extractMentionedUsers' import { validateNotifyUsers } from '../validation/validationMiddleware' import { pubsub, NOTIFICATION_ADDED } from '../../server' -const publishNotifications = async (...promises) => { - const notifications = await Promise.all(promises) +const queryNotificationsEmails = async (context, notificationUserIds) => { + if (!(notificationUserIds && notificationUserIds.length)) return [] + const userEmailCypher = ` + MATCH (user: User) + // blocked users are filtered out from notifications already + WHERE user.id in $notificationUserIds + WITH user + MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) + RETURN emailAddress {.email} + ` + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const emailAddressTransactionResponse = await transaction.run(userEmailCypher, { + notificationUserIds, + }) + return emailAddressTransactionResponse.records.map((record) => record.get('emailAddress')) + }) + try { + const emailAddresses = await writeTxResultPromise + return emailAddresses + } catch (error) { + throw new Error(error) + } finally { + session.close() + } +} + +const sendNotificationEmails = async (notification, email) => { + // Wolle + console.log('sendNotificationEmails !!! notification.to.slug: ', notification.to.slug) + console.log('sendNotificationEmails !!! email: ', email) +} + +const publishNotifications = async (context, promises) => { + let notifications = await Promise.all(promises) + notifications = notifications.flat() + // Wolle + console.log('notifications: ', notifications) + const notificationsEmailAddresses = await queryNotificationsEmails(context, notifications.map((notification) => notification.to.id)) + // Wolle + console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) notifications - .flat() - .forEach((notificationAdded) => { + .forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) // Wolle - // XXX send e-mails + // console.log('notificationAdded: ', notificationAdded) + sendNotificationEmails(notificationAdded, notificationsEmailAddresses[index].email) }) } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const idsOfUsers = extractMentionedUsers(args.content) - // Wolle console.log(context) const post = await resolve(root, args, context, resolveInfo) if (post) { - await publishNotifications( + await publishNotifications(context, [ notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context), - ) + ]) } return post } @@ -31,10 +69,10 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI const comment = await resolve(root, args, context, resolveInfo) const [postAuthor] = await postAuthorOfComment(comment.id, { context }) idsOfUsers = idsOfUsers.filter((id) => id !== postAuthor.id) - await publishNotifications( + await publishNotifications(context, [ notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context), notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context), - ) + ]) return comment } @@ -87,6 +125,10 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { } } mentionedCypher += ` + // Wolle remove !!! + // WITH notification, user, resource + // MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) + // SET user.email = emailAddress.email WITH notification, user, resource, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts From 5dfa6913c38fd79e1f91df882ef1a76e43ef0a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 11 Aug 2021 07:48:12 +0200 Subject: [PATCH 03/15] Refactor 'sendMail' to a general helper --- .../src/middleware/email/emailMiddleware.js | 40 +---------------- .../src/middleware/helpers/email/sendMail.js | 44 +++++++++++++++++++ .../{ => helpers}/email/templateBuilder.js | 4 +- .../email/templates/emailVerification.html | 0 .../{ => helpers}/email/templates/index.js | 0 .../{ => helpers}/email/templates/layout.html | 0 .../email/templates/resetPassword.html | 0 .../{ => helpers}/email/templates/signup.html | 0 .../email/templates/wrongAccount.html | 0 9 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 backend/src/middleware/helpers/email/sendMail.js rename backend/src/middleware/{ => helpers}/email/templateBuilder.js (96%) rename backend/src/middleware/{ => helpers}/email/templates/emailVerification.html (100%) rename backend/src/middleware/{ => helpers}/email/templates/index.js (100%) rename backend/src/middleware/{ => helpers}/email/templates/layout.html (100%) rename backend/src/middleware/{ => helpers}/email/templates/resetPassword.html (100%) rename backend/src/middleware/{ => helpers}/email/templates/signup.html (100%) rename backend/src/middleware/{ => helpers}/email/templates/wrongAccount.html (100%) diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/email/emailMiddleware.js index 571b733d5..b7fe0239a 100644 --- a/backend/src/middleware/email/emailMiddleware.js +++ b/backend/src/middleware/email/emailMiddleware.js @@ -1,46 +1,10 @@ -import CONFIG from '../../config' -import nodemailer from 'nodemailer' -import { htmlToText } from 'nodemailer-html-to-text' +import { sendMail } from '../helpers/email/sendMail' import { signupTemplate, resetPasswordTemplate, wrongAccountTemplate, emailVerificationTemplate, -} from './templateBuilder' - -const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT -const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD - -let sendMail = () => {} -if (!hasEmailConfig) { - if (!CONFIG.TEST) { - // eslint-disable-next-line no-console - console.log('Warning: Email middleware will not try to send mails.') - } -} else { - sendMail = async (templateArgs) => { - const transporter = nodemailer.createTransport({ - host: CONFIG.SMTP_HOST, - port: CONFIG.SMTP_PORT, - ignoreTLS: CONFIG.SMTP_IGNORE_TLS, - secure: CONFIG.SMTP_SECURE, // true for 465, false for other ports - auth: hasAuthData && { - user: CONFIG.SMTP_USERNAME, - pass: CONFIG.SMTP_PASSWORD, - }, - }) - - transporter.use( - 'compile', - htmlToText({ - ignoreImage: true, - wordwrap: false, - }), - ) - - await transporter.sendMail(templateArgs) - } -} +} from '../helpers/email/templateBuilder' const sendSignupMail = async (resolve, root, args, context, resolveInfo) => { const { inviteCode } = args diff --git a/backend/src/middleware/helpers/email/sendMail.js b/backend/src/middleware/helpers/email/sendMail.js new file mode 100644 index 000000000..9886b88e2 --- /dev/null +++ b/backend/src/middleware/helpers/email/sendMail.js @@ -0,0 +1,44 @@ +import CONFIG from '../../../config' +import nodemailer from 'nodemailer' +import { htmlToText } from 'nodemailer-html-to-text' + +const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT +const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD + +// Wolle +// let sendMailCallback = async () => {} +// Wolle +let sendMailCallback = async (templateArgs) => { + console.log('templateArgs: ', templateArgs) +} +if (!hasEmailConfig) { + if (!CONFIG.TEST) { + // eslint-disable-next-line no-console + console.log('Warning: Email middleware will not try to send mails.') // Wolle + } +} else { + sendMailCallback = async (templateArgs) => { + const transporter = nodemailer.createTransport({ + host: CONFIG.SMTP_HOST, + port: CONFIG.SMTP_PORT, + ignoreTLS: CONFIG.SMTP_IGNORE_TLS, + secure: CONFIG.SMTP_SECURE, // true for 465, false for other ports + auth: hasAuthData && { + user: CONFIG.SMTP_USERNAME, + pass: CONFIG.SMTP_PASSWORD, + }, + }) + + transporter.use( + 'compile', + htmlToText({ + ignoreImage: true, + wordwrap: false, + }), + ) + + await transporter.sendMail(templateArgs) + } +} + +export const sendMail = sendMailCallback diff --git a/backend/src/middleware/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js similarity index 96% rename from backend/src/middleware/email/templateBuilder.js rename to backend/src/middleware/helpers/email/templateBuilder.js index 872b86b29..9de26f48e 100644 --- a/backend/src/middleware/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -1,6 +1,6 @@ import mustache from 'mustache' -import CONFIG from '../../config' -import logosWebapp from '../../config/logos.js' +import CONFIG from '../../../config' +import logosWebapp from '../../../config/logos.js' import * as templates from './templates' diff --git a/backend/src/middleware/email/templates/emailVerification.html b/backend/src/middleware/helpers/email/templates/emailVerification.html similarity index 100% rename from backend/src/middleware/email/templates/emailVerification.html rename to backend/src/middleware/helpers/email/templates/emailVerification.html diff --git a/backend/src/middleware/email/templates/index.js b/backend/src/middleware/helpers/email/templates/index.js similarity index 100% rename from backend/src/middleware/email/templates/index.js rename to backend/src/middleware/helpers/email/templates/index.js diff --git a/backend/src/middleware/email/templates/layout.html b/backend/src/middleware/helpers/email/templates/layout.html similarity index 100% rename from backend/src/middleware/email/templates/layout.html rename to backend/src/middleware/helpers/email/templates/layout.html diff --git a/backend/src/middleware/email/templates/resetPassword.html b/backend/src/middleware/helpers/email/templates/resetPassword.html similarity index 100% rename from backend/src/middleware/email/templates/resetPassword.html rename to backend/src/middleware/helpers/email/templates/resetPassword.html diff --git a/backend/src/middleware/email/templates/signup.html b/backend/src/middleware/helpers/email/templates/signup.html similarity index 100% rename from backend/src/middleware/email/templates/signup.html rename to backend/src/middleware/helpers/email/templates/signup.html diff --git a/backend/src/middleware/email/templates/wrongAccount.html b/backend/src/middleware/helpers/email/templates/wrongAccount.html similarity index 100% rename from backend/src/middleware/email/templates/wrongAccount.html rename to backend/src/middleware/helpers/email/templates/wrongAccount.html From 0c8287ab037d22cc8a2e19319c6192b223338667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 11 Aug 2021 07:50:29 +0200 Subject: [PATCH 04/15] Delete test Cypher statement --- .../notifications/notificationsMiddleware.js | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index d354a7dcb..de93137df 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -40,16 +40,18 @@ const publishNotifications = async (context, promises) => { notifications = notifications.flat() // Wolle console.log('notifications: ', notifications) - const notificationsEmailAddresses = await queryNotificationsEmails(context, notifications.map((notification) => notification.to.id)) + const notificationsEmailAddresses = await queryNotificationsEmails( + context, + notifications.map((notification) => notification.to.id), + ) // Wolle console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) - notifications - .forEach((notificationAdded, index) => { - pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) - // Wolle - // console.log('notificationAdded: ', notificationAdded) - sendNotificationEmails(notificationAdded, notificationsEmailAddresses[index].email) - }) + notifications.forEach((notificationAdded, index) => { + pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) + // Wolle + // console.log('notificationAdded: ', notificationAdded) + sendNotificationEmails(notificationAdded, notificationsEmailAddresses[index].email) + }) } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { @@ -125,10 +127,6 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { } } mentionedCypher += ` - // Wolle remove !!! - // WITH notification, user, resource - // MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) - // SET user.email = emailAddress.email WITH notification, user, resource, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts From 7e72a0bf0363842c55a89009e0a299a42a0bfac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 11 Aug 2021 10:29:19 +0200 Subject: [PATCH 05/15] Implement sending of first translated templates --- .../src/middleware/helpers/email/sendMail.js | 4 +- .../helpers/email/templateBuilder.js | 32 +++++++++++ .../helpers/email/templates/de/index.js | 6 ++ .../email/templates/de/notification.html | 55 +++++++++++++++++++ .../helpers/email/templates/en/index.js | 6 ++ .../email/templates/en/notification.html | 55 +++++++++++++++++++ .../notifications/notificationsMiddleware.js | 22 ++++---- 7 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 backend/src/middleware/helpers/email/templates/de/index.js create mode 100644 backend/src/middleware/helpers/email/templates/de/notification.html create mode 100644 backend/src/middleware/helpers/email/templates/en/index.js create mode 100644 backend/src/middleware/helpers/email/templates/en/notification.html diff --git a/backend/src/middleware/helpers/email/sendMail.js b/backend/src/middleware/helpers/email/sendMail.js index 9886b88e2..a1b3b238a 100644 --- a/backend/src/middleware/helpers/email/sendMail.js +++ b/backend/src/middleware/helpers/email/sendMail.js @@ -14,10 +14,12 @@ let sendMailCallback = async (templateArgs) => { if (!hasEmailConfig) { if (!CONFIG.TEST) { // eslint-disable-next-line no-console - console.log('Warning: Email middleware will not try to send mails.') // Wolle + console.log('Warning: Middlewares will not try to send mails.') // Wolle } } else { sendMailCallback = async (templateArgs) => { + // Wolle + console.log('templateArgs: ', templateArgs) const transporter = nodemailer.createTransport({ host: CONFIG.SMTP_HOST, port: CONFIG.SMTP_PORT, diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 9de26f48e..f37e89a4b 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -3,6 +3,8 @@ import CONFIG from '../../../config' import logosWebapp from '../../../config/logos.js' import * as templates from './templates' +import * as templatesEN from './templates/en' +import * as templatesDE from './templates/de' const from = CONFIG.EMAIL_DEFAULT_SENDER const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) @@ -90,3 +92,33 @@ export const wrongAccountTemplate = ({ email }) => { ), } } + +export const notificationTemplate = ({ email, notification }) => { + const subject = `${CONFIG.APPLICATION_NAME} – Benachrichtigung | Notification` + const actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) + // TODO Wolle language + let content + switch (notification.to.locale) { + case 'de': + content = templatesDE.notification + break + case 'en': + content = templatesEN.notification + break + + default: + content = templatesEN.notification + break + } + + return { + from, + to: email, + subject, + html: mustache.render( + templates.layout, + { ...defaultParams, actionUrl, supportUrl: /* Wolle */ CONFIG.SUPPORT_URL, welcomeImageUrl }, + { content }, + ), + } +} diff --git a/backend/src/middleware/helpers/email/templates/de/index.js b/backend/src/middleware/helpers/email/templates/de/index.js new file mode 100644 index 000000000..0f9d13c36 --- /dev/null +++ b/backend/src/middleware/helpers/email/templates/de/index.js @@ -0,0 +1,6 @@ +import fs from 'fs' +import path from 'path' + +const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') + +export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html new file mode 100644 index 000000000..cb566099e --- /dev/null +++ b/backend/src/middleware/helpers/email/templates/de/notification.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/helpers/email/templates/en/index.js b/backend/src/middleware/helpers/email/templates/en/index.js new file mode 100644 index 000000000..0f9d13c36 --- /dev/null +++ b/backend/src/middleware/helpers/email/templates/en/index.js @@ -0,0 +1,6 @@ +import fs from 'fs' +import path from 'path' + +const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') + +export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/en/notification.html b/backend/src/middleware/helpers/email/templates/en/notification.html new file mode 100644 index 000000000..d49538356 --- /dev/null +++ b/backend/src/middleware/helpers/email/templates/en/notification.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index de93137df..ad0505f2e 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -1,6 +1,8 @@ +import { pubsub, NOTIFICATION_ADDED } from '../../server' import extractMentionedUsers from './mentions/extractMentionedUsers' import { validateNotifyUsers } from '../validation/validationMiddleware' -import { pubsub, NOTIFICATION_ADDED } from '../../server' +import { sendMail } from '../helpers/email/sendMail' +import { notificationTemplate } from '../helpers/email/templateBuilder' const queryNotificationsEmails = async (context, notificationUserIds) => { if (!(notificationUserIds && notificationUserIds.length)) return [] @@ -29,12 +31,6 @@ const queryNotificationsEmails = async (context, notificationUserIds) => { } } -const sendNotificationEmails = async (notification, email) => { - // Wolle - console.log('sendNotificationEmails !!! notification.to.slug: ', notification.to.slug) - console.log('sendNotificationEmails !!! email: ', email) -} - const publishNotifications = async (context, promises) => { let notifications = await Promise.all(promises) notifications = notifications.flat() @@ -48,10 +44,16 @@ const publishNotifications = async (context, promises) => { console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) - // Wolle - // console.log('notificationAdded: ', notificationAdded) - sendNotificationEmails(notificationAdded, notificationsEmailAddresses[index].email) + // Wolle await + sendMail( + notificationTemplate({ + email: notificationsEmailAddresses[index].email, + notification: notificationAdded, + }), + ) }) + // Wolle + // return XXX successful? } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { From f3bf730a12a257d685c7a9e2e784c43cbdad689b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 11 Aug 2021 14:45:20 +0200 Subject: [PATCH 06/15] Refine translated e-mail templates --- .../src/middleware/helpers/email/sendMail.js | 4 +- .../helpers/email/templateBuilder.js | 39 ++++++++++++++----- .../email/templates/de/notification.html | 27 +++++++++---- .../email/templates/en/notification.html | 28 +++++++++---- .../helpers/email/templates/layout.html | 11 +++--- .../notifications/notificationsMiddleware.js | 4 +- 6 files changed, 80 insertions(+), 33 deletions(-) diff --git a/backend/src/middleware/helpers/email/sendMail.js b/backend/src/middleware/helpers/email/sendMail.js index a1b3b238a..3fc1e2931 100644 --- a/backend/src/middleware/helpers/email/sendMail.js +++ b/backend/src/middleware/helpers/email/sendMail.js @@ -9,7 +9,7 @@ const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD // let sendMailCallback = async () => {} // Wolle let sendMailCallback = async (templateArgs) => { - console.log('templateArgs: ', templateArgs) + // console.log('templateArgs: ', templateArgs) } if (!hasEmailConfig) { if (!CONFIG.TEST) { @@ -19,7 +19,7 @@ if (!hasEmailConfig) { } else { sendMailCallback = async (templateArgs) => { // Wolle - console.log('templateArgs: ', templateArgs) + // console.log('templateArgs: ', templateArgs) const transporter = nodemailer.createTransport({ host: CONFIG.SMTP_HOST, port: CONFIG.SMTP_PORT, diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index f37e89a4b..dd4985327 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -1,5 +1,6 @@ import mustache from 'mustache' import CONFIG from '../../../config' +import metadata from '../../../config/metadata.js' import logosWebapp from '../../../config/logos.js' import * as templates from './templates' @@ -10,11 +11,13 @@ const from = CONFIG.EMAIL_DEFAULT_SENDER const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) const defaultParams = { - supportUrl: CONFIG.SUPPORT_URL, - APPLICATION_NAME: CONFIG.APPLICATION_NAME, - ORGANIZATION_URL: CONFIG.ORGANIZATION_URL, welcomeImageUrl, + APPLICATION_NAME: CONFIG.APPLICATION_NAME, + ORGANIZATION_NAME: metadata.ORGANIZATION_NAME, + ORGANIZATION_URL: CONFIG.ORGANIZATION_URL, + supportUrl: CONFIG.SUPPORT_URL, } +const englishHint = 'English version below!' export const signupTemplate = ({ email, nonce, inviteCode = null }) => { const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!` @@ -35,7 +38,7 @@ export const signupTemplate = ({ email, nonce, inviteCode = null }) => { subject, html: mustache.render( templates.layout, - { ...defaultParams, actionUrl, nonce, subject }, + { ...defaultParams, englishHint, actionUrl, nonce, subject }, { content: templates.signup }, ), } @@ -53,7 +56,7 @@ export const emailVerificationTemplate = ({ email, nonce, name }) => { subject, html: mustache.render( templates.layout, - { ...defaultParams, actionUrl, name, nonce, subject }, + { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, { content: templates.emailVerification }, ), } @@ -65,13 +68,22 @@ export const resetPasswordTemplate = ({ email, nonce, name }) => { actionUrl.searchParams.set('nonce', nonce) actionUrl.searchParams.set('email', email) + // Wolle + // console.log( + // mustache.render( + // templates.layout, + // { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, + // { content: templates.passwordReset }, + // ), + // ) + return { from, to: email, subject, html: mustache.render( templates.layout, - { ...defaultParams, actionUrl, name, nonce, subject }, + { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, { content: templates.passwordReset }, ), } @@ -87,16 +99,16 @@ export const wrongAccountTemplate = ({ email }) => { subject, html: mustache.render( templates.layout, - { ...defaultParams, actionUrl, supportUrl: CONFIG.SUPPORT_URL, welcomeImageUrl }, + { ...defaultParams, englishHint, actionUrl }, { content: templates.wrongAccount }, ), } } export const notificationTemplate = ({ email, notification }) => { + // TODO Wolle language const subject = `${CONFIG.APPLICATION_NAME} – Benachrichtigung | Notification` const actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) - // TODO Wolle language let content switch (notification.to.locale) { case 'de': @@ -111,13 +123,22 @@ export const notificationTemplate = ({ email, notification }) => { break } + // Wolle + // console.log( + // mustache.render( + // templates.layout, + // { ...defaultParams, name: notification.to.name, actionUrl }, + // { content }, + // ), + // ) + return { from, to: email, subject, html: mustache.render( templates.layout, - { ...defaultParams, actionUrl, supportUrl: /* Wolle */ CONFIG.SUPPORT_URL, welcomeImageUrl }, + { ...defaultParams, name: notification.to.name, actionUrl }, { content }, ), } diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html index cb566099e..24e2257e0 100644 --- a/backend/src/middleware/helpers/email/templates/de/notification.html +++ b/backend/src/middleware/helpers/email/templates/de/notification.html @@ -23,9 +23,9 @@ style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">

- Hallo {{ name }}!

-

Du hast mindestens eine Banachrichtigung bekommen! Mit Klick auf diesen Button - kannst Du deine Nachrichten ansehen:

+ Hallo {{ name }}, +

Du hast mindestens eine Benachrichtigung erhalten. Klick auf diesen Button, + um sie anzusehen:

@@ -35,15 +35,28 @@ Banachrichtigungen - abrufen + style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Benachrichtigungen + ansehen - -

–––––––––––––––––––––––––––––––––––––––––––––––

+ + + + + + + + + + + +
+

Bis bald bei {{APPLICATION_NAME}}!

+

– Dein {{APPLICATION_NAME}} Team

diff --git a/backend/src/middleware/helpers/email/templates/en/notification.html b/backend/src/middleware/helpers/email/templates/en/notification.html index d49538356..d32a7aecf 100644 --- a/backend/src/middleware/helpers/email/templates/en/notification.html +++ b/backend/src/middleware/helpers/email/templates/en/notification.html @@ -1,4 +1,4 @@ - + @@ -23,9 +23,8 @@ style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">

- Hello {{ name }}!

-

You have received at least one notification! By clicking on this button - you can view your messages:

+ Hello {{ name }}, +

You received at least one notification. Click on this button to view them:

@@ -35,15 +34,28 @@ - -

–––––––––––––––––––––––––––––––––––––––––––––––

+ + + + + + + + + + + +
+

See you soon on {{APPLICATION_NAME}}!

+

– The {{APPLICATION_NAME}} Team

@@ -52,4 +64,4 @@ - + diff --git a/backend/src/middleware/helpers/email/templates/layout.html b/backend/src/middleware/helpers/email/templates/layout.html index da2053a93..0c68d6309 100644 --- a/backend/src/middleware/helpers/email/templates/layout.html +++ b/backend/src/middleware/helpers/email/templates/layout.html @@ -159,7 +159,7 @@ -

English version below!

+

{{englishHint}}

{{> content}} @@ -169,10 +169,11 @@ -

- {{ORGANIZATION_NAME}} -
{{ORGANIZATION_URL}}
-

+
+ {{ORGANIZATION_NAME}} +
+
+
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index ad0505f2e..95ceb9ecf 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -35,13 +35,13 @@ const publishNotifications = async (context, promises) => { let notifications = await Promise.all(promises) notifications = notifications.flat() // Wolle - console.log('notifications: ', notifications) + // console.log('notifications: ', notifications) const notificationsEmailAddresses = await queryNotificationsEmails( context, notifications.map((notification) => notification.to.id), ) // Wolle - console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) + // console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) // Wolle await From 5e56473b0e86079ae9db24def274d1101cfd93c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 12 Aug 2021 07:27:12 +0200 Subject: [PATCH 07/15] Add note --- backend/src/middleware/notifications/notificationsMiddleware.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 95ceb9ecf..464bd47bb 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -4,6 +4,7 @@ import { validateNotifyUsers } from '../validation/validationMiddleware' import { sendMail } from '../helpers/email/sendMail' import { notificationTemplate } from '../helpers/email/templateBuilder' +// Wolle -> queryNotificationEmails const queryNotificationsEmails = async (context, notificationUserIds) => { if (!(notificationUserIds && notificationUserIds.length)) return [] const userEmailCypher = ` From 73ca174e6eb1772889cb9338009eadc979dd7046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 15 Sep 2021 13:18:54 +0200 Subject: [PATCH 08/15] Change name of emailMiddleware to loginMiddleware --- backend/src/middleware/index.js | 6 +++--- .../{email/emailMiddleware.js => login/loginMiddleware.js} | 0 webapp/components/Registration/RegistrationSlideEmail.vue | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename backend/src/middleware/{email/emailMiddleware.js => login/loginMiddleware.js} (100%) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 592e25a60..22e92e1a3 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -12,7 +12,7 @@ import orderBy from './orderByMiddleware' import validation from './validation/validationMiddleware' import notifications from './notifications/notificationsMiddleware' import hashtags from './hashtags/hashtagsMiddleware' -import email from './email/emailMiddleware' +import login from './login/loginMiddleware' import sentry from './sentryMiddleware' import languages from './languages/languages' import userInteractions from './userInteractions' @@ -26,7 +26,7 @@ export default (schema) => { validation, sluggify, excerpt, - email, + login, notifications, hashtags, softDelete, @@ -46,7 +46,7 @@ export default (schema) => { 'sluggify', 'languages', 'excerpt', - 'email', + 'login', 'notifications', 'hashtags', 'softDelete', diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/login/loginMiddleware.js similarity index 100% rename from backend/src/middleware/email/emailMiddleware.js rename to backend/src/middleware/login/loginMiddleware.js diff --git a/webapp/components/Registration/RegistrationSlideEmail.vue b/webapp/components/Registration/RegistrationSlideEmail.vue index 5289248cc..045269f00 100644 --- a/webapp/components/Registration/RegistrationSlideEmail.vue +++ b/webapp/components/Registration/RegistrationSlideEmail.vue @@ -154,7 +154,7 @@ export default { this.sliderData.setSliderValuesCallback(null, { sliderSettings: { buttonLoading: true }, }) - const response = await this.$apollo.mutate({ mutation: SignupMutation, variables }) // e-mail is send in emailMiddleware of backend + const response = await this.$apollo.mutate({ mutation: SignupMutation, variables }) // e-mail is send in loginMiddleware of backend this.sliderData.setSliderValuesCallback(null, { sliderData: { request: { variables }, response: response.data }, }) From 7ae5593f82957f514c0d0b20671b8a10b545ee57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 22 Sep 2021 14:05:10 +0200 Subject: [PATCH 09/15] Implement user notifications settings --- backend/src/db/factories.js | 1 + .../src/middleware/helpers/email/sendMail.js | 9 +-- .../notifications/notificationsMiddleware.js | 22 +++--- backend/src/models/User.js | 4 ++ backend/src/schema/resolvers/registration.js | 1 + backend/src/schema/resolvers/users.js | 1 + backend/src/schema/types/type/User.gql | 2 + webapp/graphql/User.js | 5 ++ webapp/locales/de.json | 7 +- webapp/locales/en.json | 7 +- webapp/pages/settings.vue | 4 ++ webapp/pages/settings/notifications.spec.js | 70 +++++++++++++++++++ webapp/pages/settings/notifications.vue | 61 ++++++++++++++++ 13 files changed, 171 insertions(+), 23 deletions(-) create mode 100644 webapp/pages/settings/notifications.spec.js create mode 100644 webapp/pages/settings/notifications.vue diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 64ee2009c..f3f2e5c93 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -70,6 +70,7 @@ Factory.define('basicUser') termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z', allowEmbedIframes: false, showShoutsPublicly: false, + sendNotificationEmails: true, locale: 'en', }) .attr('slug', ['slug', 'name'], (slug, name) => { diff --git a/backend/src/middleware/helpers/email/sendMail.js b/backend/src/middleware/helpers/email/sendMail.js index 3fc1e2931..2aa042bcd 100644 --- a/backend/src/middleware/helpers/email/sendMail.js +++ b/backend/src/middleware/helpers/email/sendMail.js @@ -5,12 +5,7 @@ import { htmlToText } from 'nodemailer-html-to-text' const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD -// Wolle -// let sendMailCallback = async () => {} -// Wolle -let sendMailCallback = async (templateArgs) => { - // console.log('templateArgs: ', templateArgs) -} +let sendMailCallback = async () => {} if (!hasEmailConfig) { if (!CONFIG.TEST) { // eslint-disable-next-line no-console @@ -18,8 +13,6 @@ if (!hasEmailConfig) { } } else { sendMailCallback = async (templateArgs) => { - // Wolle - // console.log('templateArgs: ', templateArgs) const transporter = nodemailer.createTransport({ host: CONFIG.SMTP_HOST, port: CONFIG.SMTP_PORT, diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 464bd47bb..af7cf19f9 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -35,26 +35,22 @@ const queryNotificationsEmails = async (context, notificationUserIds) => { const publishNotifications = async (context, promises) => { let notifications = await Promise.all(promises) notifications = notifications.flat() - // Wolle - // console.log('notifications: ', notifications) const notificationsEmailAddresses = await queryNotificationsEmails( context, notifications.map((notification) => notification.to.id), ) - // Wolle - // console.log('notificationsEmailAddresses: ', notificationsEmailAddresses) notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) - // Wolle await - sendMail( - notificationTemplate({ - email: notificationsEmailAddresses[index].email, - notification: notificationAdded, - }), - ) + if (notificationAdded.to.sendNotificationEmails) { + // Wolle await + sendMail( + notificationTemplate({ + email: notificationsEmailAddresses[index].email, + notification: notificationAdded, + }), + ) + } }) - // Wolle - // return XXX successful? } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 6cfd22268..b8d024216 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -153,6 +153,10 @@ export default { type: 'boolean', default: false, }, + sendNotificationEmails: { + type: 'boolean', + default: true, + }, locale: { type: 'string', allow: [null], diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index 2796028fe..fc504f7cd 100644 --- a/backend/src/schema/resolvers/registration.js +++ b/backend/src/schema/resolvers/registration.js @@ -93,6 +93,7 @@ const signupCypher = (inviteCode) => { SET user.updatedAt = toString(datetime()) SET user.allowEmbedIframes = FALSE SET user.showShoutsPublicly = FALSE + SET user.sendNotificationEmails = TRUE SET email.verifiedAt = toString(datetime()) RETURN user {.*} ` diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index c6bb64125..5dc78c5e1 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -290,6 +290,7 @@ export default { 'termsAndConditionsAgreedAt', 'allowEmbedIframes', 'showShoutsPublicly', + 'sendNotificationEmails', 'locale', ], boolean: { diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index b8b805a02..772dedf6b 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -46,6 +46,7 @@ type User { allowEmbedIframes: Boolean showShoutsPublicly: Boolean + sendNotificationEmails: Boolean locale: String friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)") @@ -207,6 +208,7 @@ type Mutation { termsAndConditionsAgreedAt: String allowEmbedIframes: Boolean showShoutsPublicly: Boolean + sendNotificationEmails: Boolean locale: String ): User diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 4b3a67775..b3f131238 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -41,6 +41,7 @@ export default (i18n) => { url } showShoutsPublicly + sendNotificationEmails } } ` @@ -224,6 +225,7 @@ export const updateUserMutation = () => { $about: String $allowEmbedIframes: Boolean $showShoutsPublicly: Boolean + $sendNotificationEmails: Boolean $termsAndConditionsAgreedVersion: String $avatar: ImageInput ) { @@ -235,6 +237,7 @@ export const updateUserMutation = () => { about: $about allowEmbedIframes: $allowEmbedIframes showShoutsPublicly: $showShoutsPublicly + sendNotificationEmails: $sendNotificationEmails termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion avatar: $avatar ) { @@ -245,6 +248,7 @@ export const updateUserMutation = () => { about allowEmbedIframes showShoutsPublicly + sendNotificationEmails locale termsAndConditionsAgreedVersion avatar { @@ -275,6 +279,7 @@ export const currentUserQuery = gql` locale allowEmbedIframes showShoutsPublicly + sendNotificationEmails termsAndConditionsAgreedVersion socialMedia { id diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 733a42629..e4e7332e2 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -739,13 +739,18 @@ "unmuted": "{name} ist nicht mehr stummgeschaltet" }, "name": "Einstellungen", + "notifications": { + "send-email-notifications": "Sende E-Mail-Benachrichtigungen", + "name": "Benachrichtigungen", + "success-update": "Benachrichtigungs-Einstellungen gespeichert!" + }, "organizations": { "name": "Meine Organisationen" }, "privacy": { "make-shouts-public": "Teile von mir empfohlene Artikel öffentlich auf meinem Profil", "name": "Privatsphäre", - "success-update": "Privatsphäre-Einstellungen gespeichert" + "success-update": "Privatsphäre-Einstellungen gespeichert!" }, "security": { "change-password": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 3c26cd93e..d9f899664 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -739,13 +739,18 @@ "unmuted": "{name} is unmuted again" }, "name": "Settings", + "notifications": { + "send-email-notifications": "Send e-mail notifications", + "name": "Notifications", + "success-update": "Notifications settings saved!" + }, "organizations": { "name": "My Organizations" }, "privacy": { "make-shouts-public": "Share articles I have shouted on my public profile", "name": "Privacy", - "success-update": "Privacy settings saved" + "success-update": "Privacy settings saved!" }, "security": { "change-password": { diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue index 6bd78b701..360f1e969 100644 --- a/webapp/pages/settings.vue +++ b/webapp/pages/settings.vue @@ -51,6 +51,10 @@ export default { name: this.$t('settings.embeds.name'), path: `/settings/embeds`, }, + { + name: this.$t('settings.notifications.name'), + path: '/settings/notifications', + }, { name: this.$t('settings.download.name'), path: `/settings/data-download`, diff --git a/webapp/pages/settings/notifications.spec.js b/webapp/pages/settings/notifications.spec.js new file mode 100644 index 000000000..7b43ef2c4 --- /dev/null +++ b/webapp/pages/settings/notifications.spec.js @@ -0,0 +1,70 @@ +import Vuex from 'vuex' +import { mount } from '@vue/test-utils' +import Notifications from './notifications.vue' + +const localVue = global.localVue + +describe('notifications.vue', () => { + let wrapper + let mocks + let store + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest.fn(), + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { + id: 'u343', + name: 'MyAccount', + sendNotificationEmails: true, + } + }, + }, + }) + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(Notifications, { + store, + mocks, + localVue, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.is('.base-card')).toBe(true) + }) + + it('clicking on submit changes notifyByEmail to false', async () => { + wrapper.find('#send-email').trigger('click') + await wrapper.vm.$nextTick() + wrapper.find('.base-button').trigger('click') + expect(wrapper.vm.notifyByEmail).toBe(false) + }) + + it('clicking on submit with a server error shows a toast and notifyByEmail is still true', async () => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' }) + wrapper.find('#send-email').trigger('click') + await wrapper.vm.$nextTick() + await wrapper.find('.base-button').trigger('click') + await wrapper.vm.$nextTick() + expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!') + expect(wrapper.vm.notifyByEmail).toBe(true) + }) + }) +}) diff --git a/webapp/pages/settings/notifications.vue b/webapp/pages/settings/notifications.vue new file mode 100644 index 000000000..3d1a7eb62 --- /dev/null +++ b/webapp/pages/settings/notifications.vue @@ -0,0 +1,61 @@ + + + From d8b23d9df2747791bf564176180a532c5f90ef1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 22 Sep 2021 14:11:03 +0200 Subject: [PATCH 10/15] Fix locales sorting --- webapp/locales/de.json | 2 +- webapp/locales/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index e4e7332e2..9816e167a 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -740,8 +740,8 @@ }, "name": "Einstellungen", "notifications": { - "send-email-notifications": "Sende E-Mail-Benachrichtigungen", "name": "Benachrichtigungen", + "send-email-notifications": "Sende E-Mail-Benachrichtigungen", "success-update": "Benachrichtigungs-Einstellungen gespeichert!" }, "organizations": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index d9f899664..11b920056 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -740,8 +740,8 @@ }, "name": "Settings", "notifications": { - "send-email-notifications": "Send e-mail notifications", "name": "Notifications", + "send-email-notifications": "Send e-mail notifications", "success-update": "Notifications settings saved!" }, "organizations": { From a87cc524a641fbde0de8f0f868d05d44eea4aacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 23 Sep 2021 14:04:58 +0200 Subject: [PATCH 11/15] Translate e-mail subject --- .../helpers/email/templateBuilder.js | 18 ++++++------------ .../email/templates/de/notification.html | 1 + .../email/templates/en/notification.html | 1 + 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index dd4985327..0d9972c76 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -106,9 +106,8 @@ export const wrongAccountTemplate = ({ email }) => { } export const notificationTemplate = ({ email, notification }) => { - // TODO Wolle language - const subject = `${CONFIG.APPLICATION_NAME} – Benachrichtigung | Notification` const actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) + const renderParams = { ...defaultParams, name: notification.to.name, actionUrl } let content switch (notification.to.locale) { case 'de': @@ -122,24 +121,19 @@ export const notificationTemplate = ({ email, notification }) => { content = templatesEN.notification break } + const subjectUnrendered = content.split('\n')[0].split('"')[1] + const subject = mustache.render(subjectUnrendered, renderParams, {}) + // Wolle console.log('subject: ', subject) // Wolle // console.log( - // mustache.render( - // templates.layout, - // { ...defaultParams, name: notification.to.name, actionUrl }, - // { content }, - // ), + // mustache.render(templates.layout, renderParams, { content }), // ) return { from, to: email, subject, - html: mustache.render( - templates.layout, - { ...defaultParams, name: notification.to.name, actionUrl }, - { content }, - ), + html: mustache.render(templates.layout, renderParams, { content }), } } diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html index 24e2257e0..1c8092f9d 100644 --- a/backend/src/middleware/helpers/email/templates/de/notification.html +++ b/backend/src/middleware/helpers/email/templates/de/notification.html @@ -1,3 +1,4 @@ + diff --git a/backend/src/middleware/helpers/email/templates/en/notification.html b/backend/src/middleware/helpers/email/templates/en/notification.html index d32a7aecf..bd4cbb8a7 100644 --- a/backend/src/middleware/helpers/email/templates/en/notification.html +++ b/backend/src/middleware/helpers/email/templates/en/notification.html @@ -1,3 +1,4 @@ + From 550a06e6fad7a05398fd1a8761b320cd12c9f506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 23 Sep 2021 14:41:39 +0200 Subject: [PATCH 12/15] Add text and link to the e-mail content to change e-mail notification settings --- .../middleware/helpers/email/templateBuilder.js | 4 +--- .../helpers/email/templates/de/notification.html | 15 +++++++++++++++ .../helpers/email/templates/en/notification.html | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 0d9972c76..d71202d45 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -126,9 +126,7 @@ export const notificationTemplate = ({ email, notification }) => { // Wolle console.log('subject: ', subject) // Wolle - // console.log( - // mustache.render(templates.layout, renderParams, { content }), - // ) + // console.log(mustache.render(templates.layout, renderParams, { content })) return { from, diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html index 1c8092f9d..b3c60b26e 100644 --- a/backend/src/middleware/helpers/email/templates/de/notification.html +++ b/backend/src/middleware/helpers/email/templates/de/notification.html @@ -65,5 +65,20 @@ + + + + + + diff --git a/backend/src/middleware/helpers/email/templates/en/notification.html b/backend/src/middleware/helpers/email/templates/en/notification.html index bd4cbb8a7..58cbffd6f 100644 --- a/backend/src/middleware/helpers/email/templates/en/notification.html +++ b/backend/src/middleware/helpers/email/templates/en/notification.html @@ -64,5 +64,20 @@ + + + + + + + +
+

PS: If you don't want to receive e-mails anymore, change your notification settings.

+
+ + + + From d5468e51239ddbd6f90a4417fbbc9a27d07bd5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 23 Sep 2021 15:01:20 +0200 Subject: [PATCH 13/15] Add db mutation for adding sendNotificationEmails property to each user --- ...otificationEmails-property-to-all-users.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.js diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.js b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.js new file mode 100644 index 000000000..0d1f4fb91 --- /dev/null +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.js @@ -0,0 +1,59 @@ +import { getDriver } from '../../db/neo4j' + +export const description = '' + +export async function up(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + // Implement your migration here. + await transaction.run( + ` + MATCH (user:User) + SET user.sendNotificationEmails = true + RETURN user {.*} + `, + ) + await transaction.commit() + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} + +export async function down(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + // Implement your migration here. + await transaction.run( + ` + MATCH (user:User) + REMOVE user.sendNotificationEmails + RETURN user {.*} + `, + ) + await transaction.commit() + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} From e58b452a7d4f57c94aa970a2d93093618c67a683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 27 Sep 2021 12:15:18 +0200 Subject: [PATCH 14/15] Write Cypress tests --- .../User.SettingNotifications.feature | 20 +++++++++++++++++++ .../I_click_on_element_with_ID_{string}.js | 5 +++++ .../User.SettingNotifications/I_click_save.js | 5 +++++ ...ox_show_donations_progress_bar_and_save.js | 6 ++++++ ..._the_form copy.js => I_submit_the_form.js} | 0 .../I_see_a_{string}_message.js | 2 +- ...eckbox_with_ID_{string}_should_{string}.js | 5 +++++ webapp/pages/settings/notifications.vue | 4 +++- 8 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 cypress/integration/User.SettingNotifications.feature create mode 100644 cypress/integration/User.SettingNotifications/I_click_on_element_with_ID_{string}.js create mode 100644 cypress/integration/User.SettingNotifications/I_click_save.js create mode 100644 cypress/integration/User.SettingNotifications/I_click_the_checkbox_show_donations_progress_bar_and_save.js rename cypress/integration/UserProfile.ChangePassword/{I_submit_the_form copy.js => I_submit_the_form.js} (100%) rename cypress/integration/{UserProfile.ChangePassword => common}/I_see_a_{string}_message.js (61%) create mode 100644 cypress/integration/common/the_checkbox_with_ID_{string}_should_{string}.js diff --git a/cypress/integration/User.SettingNotifications.feature b/cypress/integration/User.SettingNotifications.feature new file mode 100644 index 000000000..7e4301b81 --- /dev/null +++ b/cypress/integration/User.SettingNotifications.feature @@ -0,0 +1,20 @@ +Feature: User sets donations info settings + As a user + I want to change my notifications settings + In order to manage the notifications + + Background: + Given the following "users" are in the database: + | email | password | id | name | slug | termsAndConditionsAgreedVersion | + | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 | + | user@example.org | 123 | user | User | user | 0.0.4 | + And I am logged in as "peter-pan" + + Scenario: The notifications setting "Send e-mail notifications" is set to true by default and can be set to false + # When I navigate to my "Notifications" settings page + When I navigate to page "/settings/notifications" + Then the checkbox with ID "send-email" should "be.checked" + And I click on element with ID "send-email" + And the checkbox with ID "send-email" should "not.be.checked" + Then I click save + And I see a toaster with "Notifications settings saved!" diff --git a/cypress/integration/User.SettingNotifications/I_click_on_element_with_ID_{string}.js b/cypress/integration/User.SettingNotifications/I_click_on_element_with_ID_{string}.js new file mode 100644 index 000000000..251c38941 --- /dev/null +++ b/cypress/integration/User.SettingNotifications/I_click_on_element_with_ID_{string}.js @@ -0,0 +1,5 @@ +import { When } from "cypress-cucumber-preprocessor/steps"; + +When("I click on element with ID {string}", (id) => { + cy.get('#' + id).click() +}) diff --git a/cypress/integration/User.SettingNotifications/I_click_save.js b/cypress/integration/User.SettingNotifications/I_click_save.js new file mode 100644 index 000000000..32d702f1e --- /dev/null +++ b/cypress/integration/User.SettingNotifications/I_click_save.js @@ -0,0 +1,5 @@ +import { Then } from "cypress-cucumber-preprocessor/steps"; + +Then("I click save", () => { + cy.get(".save-button").click() +}) \ No newline at end of file diff --git a/cypress/integration/User.SettingNotifications/I_click_the_checkbox_show_donations_progress_bar_and_save.js b/cypress/integration/User.SettingNotifications/I_click_the_checkbox_show_donations_progress_bar_and_save.js new file mode 100644 index 000000000..b4289dd5e --- /dev/null +++ b/cypress/integration/User.SettingNotifications/I_click_the_checkbox_show_donations_progress_bar_and_save.js @@ -0,0 +1,6 @@ +import { Then } from "cypress-cucumber-preprocessor/steps"; + +Then("I click the checkbox show donations progress bar and save", () => { + cy.get("#showDonations").click() + cy.get(".donations-info-button").click() +}) \ No newline at end of file diff --git a/cypress/integration/UserProfile.ChangePassword/I_submit_the_form copy.js b/cypress/integration/UserProfile.ChangePassword/I_submit_the_form.js similarity index 100% rename from cypress/integration/UserProfile.ChangePassword/I_submit_the_form copy.js rename to cypress/integration/UserProfile.ChangePassword/I_submit_the_form.js diff --git a/cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js b/cypress/integration/common/I_see_a_{string}_message.js similarity index 61% rename from cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js rename to cypress/integration/common/I_see_a_{string}_message.js index 90ddf0bd3..6cc2cbf6b 100644 --- a/cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js +++ b/cypress/integration/common/I_see_a_{string}_message.js @@ -1,5 +1,5 @@ import { Then } from "cypress-cucumber-preprocessor/steps"; -Then("I see a {string} message:", (type, message) => { +Then("I see a {string} message:", (_type, message) => { cy.contains(message); }); \ No newline at end of file diff --git a/cypress/integration/common/the_checkbox_with_ID_{string}_should_{string}.js b/cypress/integration/common/the_checkbox_with_ID_{string}_should_{string}.js new file mode 100644 index 000000000..ad3f7f3cc --- /dev/null +++ b/cypress/integration/common/the_checkbox_with_ID_{string}_should_{string}.js @@ -0,0 +1,5 @@ +import { When } from "cypress-cucumber-preprocessor/steps"; + +When("the checkbox with ID {string} should {string}", (id, value) => { + cy.get('#' + id).should(value) +}) diff --git a/webapp/pages/settings/notifications.vue b/webapp/pages/settings/notifications.vue index 3d1a7eb62..a2828a1a9 100644 --- a/webapp/pages/settings/notifications.vue +++ b/webapp/pages/settings/notifications.vue @@ -5,7 +5,9 @@ - {{ $t('actions.save') }} + + {{ $t('actions.save') }} + From a836872471dcc48ea18b5421f4f520ca71aacc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 27 Sep 2021 12:36:58 +0200 Subject: [PATCH 15/15] Cleanup --- backend/src/middleware/helpers/email/sendMail.js | 2 +- .../src/middleware/helpers/email/templateBuilder.js | 13 ------------- .../notifications/notificationsMiddleware.js | 5 ++--- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/backend/src/middleware/helpers/email/sendMail.js b/backend/src/middleware/helpers/email/sendMail.js index 2aa042bcd..a25938a14 100644 --- a/backend/src/middleware/helpers/email/sendMail.js +++ b/backend/src/middleware/helpers/email/sendMail.js @@ -9,7 +9,7 @@ let sendMailCallback = async () => {} if (!hasEmailConfig) { if (!CONFIG.TEST) { // eslint-disable-next-line no-console - console.log('Warning: Middlewares will not try to send mails.') // Wolle + console.log('Warning: Middlewares will not try to send mails.') } } else { sendMailCallback = async (templateArgs) => { diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index d71202d45..8098b38fe 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -68,15 +68,6 @@ export const resetPasswordTemplate = ({ email, nonce, name }) => { actionUrl.searchParams.set('nonce', nonce) actionUrl.searchParams.set('email', email) - // Wolle - // console.log( - // mustache.render( - // templates.layout, - // { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, - // { content: templates.passwordReset }, - // ), - // ) - return { from, to: email, @@ -123,10 +114,6 @@ export const notificationTemplate = ({ email, notification }) => { } const subjectUnrendered = content.split('\n')[0].split('"')[1] const subject = mustache.render(subjectUnrendered, renderParams, {}) - // Wolle console.log('subject: ', subject) - - // Wolle - // console.log(mustache.render(templates.layout, renderParams, { content })) return { from, diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index af7cf19f9..2bc53ab7c 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -4,8 +4,7 @@ import { validateNotifyUsers } from '../validation/validationMiddleware' import { sendMail } from '../helpers/email/sendMail' import { notificationTemplate } from '../helpers/email/templateBuilder' -// Wolle -> queryNotificationEmails -const queryNotificationsEmails = async (context, notificationUserIds) => { +const queryNotificationEmails = async (context, notificationUserIds) => { if (!(notificationUserIds && notificationUserIds.length)) return [] const userEmailCypher = ` MATCH (user: User) @@ -35,7 +34,7 @@ const queryNotificationsEmails = async (context, notificationUserIds) => { const publishNotifications = async (context, promises) => { let notifications = await Promise.all(promises) notifications = notifications.flat() - const notificationsEmailAddresses = await queryNotificationsEmails( + const notificationsEmailAddresses = await queryNotificationEmails( context, notifications.map((notification) => notification.to.id), )