Implement user notifications settings

This commit is contained in:
Wolfgang Huß 2021-09-22 14:05:10 +02:00
parent bd10149e05
commit 7ae5593f82
13 changed files with 171 additions and 23 deletions

View File

@ -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) => {

View File

@ -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,

View File

@ -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) => {

View File

@ -153,6 +153,10 @@ export default {
type: 'boolean',
default: false,
},
sendNotificationEmails: {
type: 'boolean',
default: true,
},
locale: {
type: 'string',
allow: [null],

View File

@ -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 {.*}
`

View File

@ -290,6 +290,7 @@ export default {
'termsAndConditionsAgreedAt',
'allowEmbedIframes',
'showShoutsPublicly',
'sendNotificationEmails',
'locale',
],
boolean: {

View File

@ -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

View File

@ -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

View File

@ -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": {

View File

@ -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": {

View File

@ -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`,

View File

@ -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)
})
})
})

View File

@ -0,0 +1,61 @@
<template>
<base-card>
<h2 class="title">{{ $t('settings.notifications.name') }}</h2>
<ds-space margin-bottom="small">
<input id="send-email" type="checkbox" v-model="notifyByEmail" />
<label for="send-email">{{ $t('settings.notifications.send-email-notifications') }}</label>
</ds-space>
<base-button filled @click="submit" :disabled="disabled">{{ $t('actions.save') }}</base-button>
</base-card>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { updateUserMutation } from '~/graphql/User'
export default {
data() {
return {
notifyByEmail: false,
}
},
computed: {
...mapGetters({
currentUser: 'auth/user',
}),
disabled() {
return this.notifyByEmail === this.currentUser.sendNotificationEmails
},
},
created() {
this.notifyByEmail = this.currentUser.sendNotificationEmails || false
},
methods: {
...mapMutations({
setCurrentUser: 'auth/SET_USER',
}),
async submit() {
try {
await this.$apollo.mutate({
mutation: updateUserMutation(),
variables: {
id: this.currentUser.id,
sendNotificationEmails: this.notifyByEmail,
},
update: (_, { data: { UpdateUser } }) => {
const { sendNotificationEmails } = UpdateUser
this.setCurrentUser({
...this.currentUser,
sendNotificationEmails,
})
this.$toast.success(this.$t('settings.notifications.success-update'))
},
})
} catch (error) {
this.notifyByEmail = !this.notifyByEmail
this.$toast.error(error.message)
}
},
},
}
</script>