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 @@
+
+
+ {{ $t('settings.notifications.name') }}
+
+
+
+
+ {{ $t('actions.save') }}
+
+
+
+