mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
feat(backend): emails for notifications (#8435)
* email templates with pug for all possible notification emails * more information in emails * Individual email subjects to all notification emails --------- Co-authored-by: Ulf Gebhardt <ulf.gebhardt@webcraft-media.de> Co-authored-by: mahula <lenzmath@posteo.de>
This commit is contained in:
parent
4f05b852af
commit
e4ae0dfe50
4
.github/workflows/test-e2e.yml
vendored
4
.github/workflows/test-e2e.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp frontend/.env.dist frontend/.env
|
||||
cp backend/.env.template backend/.env
|
||||
cp backend/.env.test_e2e backend/.env
|
||||
|
||||
- name: Build docker images
|
||||
run: |
|
||||
@ -77,7 +77,7 @@ jobs:
|
||||
docker load < /tmp/images/neo4j.tar
|
||||
docker load < /tmp/images/backend.tar
|
||||
docker load < /tmp/images/webapp.tar
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend --build
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --build --detach --no-deps webapp neo4j backend mailserver
|
||||
sleep 90s
|
||||
|
||||
- name: Full stack tests | run tests
|
||||
|
||||
41
backend/.env.test_e2e
Normal file
41
backend/.env.test_e2e
Normal file
@ -0,0 +1,41 @@
|
||||
DEBUG=true
|
||||
|
||||
NEO4J_URI=bolt://localhost:7687
|
||||
NEO4J_USERNAME=neo4j
|
||||
NEO4J_PASSWORD=letmein
|
||||
GRAPHQL_URI=http://localhost:4000
|
||||
CLIENT_URI=http://localhost:3000
|
||||
|
||||
# E-Mail default settings
|
||||
EMAIL_SUPPORT="devops@ocelot.social"
|
||||
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
|
||||
SMTP_HOST=mailserver
|
||||
SMTP_PORT=1025
|
||||
SMTP_IGNORE_TLS=true
|
||||
SMTP_MAX_CONNECTIONS=5
|
||||
SMTP_MAX_MESSAGES=Infinity
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_SECURE="false" # true for 465, false for other ports
|
||||
SMTP_DKIM_DOMAINNAME=
|
||||
SMTP_DKIM_KEYSELECTOR=
|
||||
SMTP_DKIM_PRIVATKEY=
|
||||
|
||||
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
||||
JWT_EXPIRES="2y"
|
||||
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
|
||||
|
||||
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
|
||||
|
||||
SENTRY_DSN_BACKEND=
|
||||
COMMIT=
|
||||
PUBLIC_REGISTRATION=false
|
||||
INVITE_REGISTRATION=true
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_ENDPOINT=
|
||||
AWS_REGION=
|
||||
AWS_BUCKET=
|
||||
|
||||
CATEGORIES_ACTIVE=false
|
||||
@ -36,6 +36,7 @@
|
||||
"cheerio": "~1.0.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"dotenv": "~16.5.0",
|
||||
"email-templates": "^12.0.2",
|
||||
"express": "^5.1.0",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
@ -77,6 +78,8 @@
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nodemailer-html-to-text": "^3.2.0",
|
||||
"preview-email": "^3.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"request": "~2.88.2",
|
||||
"sanitize-html": "~2.16.0",
|
||||
"slug": "~9.1.0",
|
||||
@ -88,6 +91,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
|
||||
"@faker-js/faker": "9.7.0",
|
||||
"@types/email-templates": "^10.0.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22.15.3",
|
||||
@ -119,7 +123,10 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"**/**/fs-capacitor": "^6.2.0",
|
||||
"**/graphql-upload": "^11.0.0"
|
||||
"**/graphql-upload": "^11.0.0",
|
||||
"**/strip-ansi": "6.0.1",
|
||||
"**/string-width": "4.2.0",
|
||||
"**/wrap-ansi": "7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.12.1"
|
||||
|
||||
@ -25,6 +25,7 @@ const environment = {
|
||||
DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV as string)
|
||||
? (env.DISABLED_MIDDLEWARES?.split(',') ?? [])
|
||||
: [],
|
||||
SEND_MAIL: env.NODE_ENV !== 'test',
|
||||
}
|
||||
|
||||
const required = {
|
||||
|
||||
@ -26,6 +26,8 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
throw new Error(`You cannot seed the database in a non-staging and real production environment!`)
|
||||
}
|
||||
|
||||
CONFIG.SEND_MAIL = true
|
||||
|
||||
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
|
||||
;(async function () {
|
||||
|
||||
32
backend/src/db/types/User.ts
Normal file
32
backend/src/db/types/User.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Integer, Node } from 'neo4j-driver'
|
||||
|
||||
export interface UserDbProperties {
|
||||
allowEmbedIframes: boolean
|
||||
awaySince?: string
|
||||
createdAt: string
|
||||
deleted: boolean
|
||||
disabled: boolean
|
||||
emailNotificationsChatMessage?: boolean
|
||||
emailNotificationsCommentOnObservedPost?: boolean
|
||||
emailNotificationsFollowingUsers?: boolean
|
||||
emailNotificationsGroupMemberJoined?: boolean
|
||||
emailNotificationsGroupMemberLeft?: boolean
|
||||
emailNotificationsGroupMemberRemoved?: boolean
|
||||
emailNotificationsGroupMemberRoleChanged?: boolean
|
||||
emailNotificationsMention?: boolean
|
||||
emailNotificationsPostInGroup?: boolean
|
||||
encryptedPassword: string
|
||||
id: string
|
||||
lastActiveAt?: string
|
||||
lastOnlineStatus?: string
|
||||
locale: string
|
||||
name: string
|
||||
role: string
|
||||
showShoutsPublicly: boolean
|
||||
slug: string
|
||||
termsAndConditionsAgreedAt: string
|
||||
termsAndConditionsAgreedVersion: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type User = Node<Integer, UserDbProperties>
|
||||
@ -0,0 +1,247 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sendChatMessageMail English chat_message template 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hello chatReceiver,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>you have received a new chat message from <a class="user" href="http://webapp:3000/user/chatSender/chatsender">chatSender</a>.
|
||||
</p><a class="button" href="http://webapp:3000/chat">Show Chat</a>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p><br>
|
||||
<p>PS: If you don't want to receive e-mails anymore, change your <a class="settings" href="http://webapp:3000/settings/notifications">notification settings</a>!</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "ocelot.social – Notification: New chat message",
|
||||
"text": "HELLO CHATRECEIVER,
|
||||
|
||||
you have received a new chat message from chatSender
|
||||
[http://webapp:3000/user/chatSender/chatsender].
|
||||
|
||||
Show Chat [http://webapp:3000/chat]
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
PS: If you don't want to receive e-mails anymore, change your notification
|
||||
settings [http://webapp:3000/settings/notifications]!
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendChatMessageMail German chat_message template 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hallo chatReceiver,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>du hast eine neue Chat-Nachricht von <a class="user" href="http://webapp:3000/user/chatSender/chatsender">chatSender</a> erhalten.
|
||||
</p><a class="button" href="http://webapp:3000/chat">Chat anzeigen</a>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p><br>
|
||||
<p>PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine <a class="settings" href="http://webapp:3000/settings/notifications">Benachrichtigungseinstellungen</a>!</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "ocelot.social – Benachrichtigung: Neue Chat Nachricht",
|
||||
"text": "HALLO CHATRECEIVER,
|
||||
|
||||
du hast eine neue Chat-Nachricht von chatSender
|
||||
[http://webapp:3000/user/chatSender/chatsender] erhalten.
|
||||
|
||||
Chat anzeigen [http://webapp:3000/chat]
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
|
||||
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
2209
backend/src/emails/__snapshots__/sendNotificationMail.spec.ts.snap
Normal file
2209
backend/src/emails/__snapshots__/sendNotificationMail.spec.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
39
backend/src/emails/locales/de.json
Normal file
39
backend/src/emails/locales/de.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"notification": "Benachrichtigung",
|
||||
"subjects": {
|
||||
"changedGroupMemberRole": "Rolle in Gruppe geändert",
|
||||
"chatMessage": "Neue Chat Nachricht",
|
||||
"commentedOnPost": "Neuer Kommentar zu Beitrag",
|
||||
"followedUserPosted": "Neuer Beitrag von gefolgtem Nutzer",
|
||||
"mentionedInComment": "Erwähnung in Kommentar",
|
||||
"mentionedInPost": "Erwähnung in Beitrag",
|
||||
"removedUserFromGroup": "Aus Gruppe entfernt",
|
||||
"postInGroup": "Neuer Beitrag in Gruppe",
|
||||
"userJoinedGroup": "Nutzer tritt Gruppe bei",
|
||||
"userLeftGroup": "Nutzer verlässt Gruppe"
|
||||
},
|
||||
"buttons": {
|
||||
"viewChat": "Chat anzeigen",
|
||||
"viewComment": "Kommentar ansehen",
|
||||
"viewGroup": "Gruppe ansehen",
|
||||
"viewPost": "Beitrag ansehen"
|
||||
},
|
||||
"general": {
|
||||
"greeting": "Hallo",
|
||||
"seeYou": "Bis bald bei ",
|
||||
"yourTeam": "– Dein {team} Team",
|
||||
"settingsHint": "PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine ",
|
||||
"settingsName": "Benachrichtigungseinstellungen"
|
||||
},
|
||||
"changedGroupMemberRole": "deine Rolle in der Gruppe „{groupName}“ wurde geändert. Klicke auf den Knopf, um diese Gruppe zu sehen:",
|
||||
"chatMessageStart": "du hast eine neue Chat-Nachricht von ",
|
||||
"chatMessageEnd": " erhalten.",
|
||||
"commentedOnPost": " hat einen Beitrag den du beobachtest mit dem Titel „{postTitle}“ kommentiert. Klicke auf den Knopf, um diesen Kommentar zu sehen:",
|
||||
"followedUserPosted": ", ein Nutzer dem du folgst, hat einen neuen Beitrag mit dem Titel „{postTitle}“ geschrieben. Klicke auf den Knopf, um diesen Beitrag zu sehen:",
|
||||
"mentionedInComment": " hat dich in einem Kommentar zu dem Beitrag mit dem Titel „{postTitle}“ erwähnt. Klicke auf den Knopf, um den Kommentar zu sehen:",
|
||||
"mentionedInPost": " hat Dich in einem Beitrag mit dem Titel „{postTitle}“ erwähnt. Klicke auf den Knopf, um den Beitrag zu sehen:",
|
||||
"postInGroup": "jemand hat einen neuen Beitrag mit dem Titel „{postTitle}“ in einer deiner Gruppen geschrieben. Klicke auf den Knopf, um diesen Beitrag zu sehen:",
|
||||
"removedUserFromGroup": "du wurdest aus der Gruppe „{groupName}“ entfernt.",
|
||||
"userJoinedGroup": " ist der Gruppe „{groupName}“ beigetreten. Klicke auf den Knopf, um diese Gruppe zu sehen:",
|
||||
"userLeftGroup": " hat die Gruppe „{groupName}“ verlassen. Klicke auf den Knopf, um diese Gruppe zu sehen:"
|
||||
}
|
||||
39
backend/src/emails/locales/en.json
Normal file
39
backend/src/emails/locales/en.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"notification": "Notification",
|
||||
"subjects": {
|
||||
"changedGroupMemberRole": "Role in group changed",
|
||||
"chatMessage": "New chat message",
|
||||
"commentedOnPost": "New comment on post",
|
||||
"followedUserPosted": "New post by followd user",
|
||||
"mentionedInComment": "Mentioned in comment",
|
||||
"mentionedInPost": "Mentioned in post",
|
||||
"removedUserFromGroup": "Removed from group",
|
||||
"postInGroup": "New post in group",
|
||||
"userJoinedGroup": "User joined group",
|
||||
"userLeftGroup": "User left group"
|
||||
},
|
||||
"buttons": {
|
||||
"viewChat": "Show Chat",
|
||||
"viewComment": "View comment",
|
||||
"viewGroup": "View group",
|
||||
"viewPost": "View post"
|
||||
},
|
||||
"general": {
|
||||
"greeting": "Hello",
|
||||
"seeYou": "See you soon on ",
|
||||
"yourTeam": "– The {team} Team",
|
||||
"settingsHint": "PS: If you don't want to receive e-mails anymore, change your ",
|
||||
"settingsName": "notification settings"
|
||||
},
|
||||
"changedGroupMemberRole": "your role in the group “{groupName}” has been changed. Click on the button to view this group:",
|
||||
"chatMessageStart": "you have received a new chat message from ",
|
||||
"chatMessageEnd": ".",
|
||||
"commentedOnPost": " commented on a post that you are observing with the title “{postTitle}”. Click on the button to view this comment:",
|
||||
"followedUserPosted": ", a user you are following, wrote a new post with the title “{postTitle}”. Click on the button to view this post:",
|
||||
"mentionedInComment": " mentioned you in a comment to the post with the title “{postTitle}”. Click on the button to view this comment:",
|
||||
"mentionedInPost": " mentioned you in a post with the title “{postTitle}”. Click on the button to view this post:",
|
||||
"removedUserFromGroup": "you have been removed from the group “{groupName}”.",
|
||||
"postInGroup": "someone wrote a new post with the title “{postTitle}” in one of your groups. Click on the button to view this post:",
|
||||
"userJoinedGroup": " joined the group “{groupName}”. Click on the button to view this group:",
|
||||
"userLeftGroup": " left the group “{groupName}”. Click on the button to view this group:"
|
||||
}
|
||||
87
backend/src/emails/sendChatMessageMail.spec.ts
Normal file
87
backend/src/emails/sendChatMessageMail.spec.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { sendChatMessageMail } from './sendEmail'
|
||||
|
||||
const senderUser = {
|
||||
allowEmbedIframes: false,
|
||||
createdAt: '2025-04-30T00:16:49.610Z',
|
||||
deleted: false,
|
||||
disabled: false,
|
||||
emailNotificationsChatMessage: true,
|
||||
emailNotificationsCommentOnObservedPost: true,
|
||||
emailNotificationsFollowingUsers: true,
|
||||
emailNotificationsGroupMemberJoined: true,
|
||||
emailNotificationsGroupMemberLeft: true,
|
||||
emailNotificationsGroupMemberRemoved: true,
|
||||
emailNotificationsGroupMemberRoleChanged: true,
|
||||
emailNotificationsMention: true,
|
||||
emailNotificationsPostInGroup: true,
|
||||
encryptedPassword: '$2b$10$n.WujXapJrvn498lS97MD.gn8QwjWI9xlf8ckEYYtMTOPadMidcbG',
|
||||
id: 'chatSender',
|
||||
locale: 'en',
|
||||
name: 'chatSender',
|
||||
role: 'user',
|
||||
showShoutsPublicly: false,
|
||||
slug: 'chatsender',
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
updatedAt: '2025-04-30T00:16:49.610Z',
|
||||
}
|
||||
|
||||
const recipientUser = {
|
||||
allowEmbedIframes: false,
|
||||
createdAt: '2025-04-30T00:16:49.716Z',
|
||||
deleted: false,
|
||||
disabled: false,
|
||||
emailNotificationsChatMessage: true,
|
||||
emailNotificationsCommentOnObservedPost: true,
|
||||
emailNotificationsFollowingUsers: true,
|
||||
emailNotificationsGroupMemberJoined: true,
|
||||
emailNotificationsGroupMemberLeft: true,
|
||||
emailNotificationsGroupMemberRemoved: true,
|
||||
emailNotificationsGroupMemberRoleChanged: true,
|
||||
emailNotificationsMention: true,
|
||||
emailNotificationsPostInGroup: true,
|
||||
encryptedPassword: '$2b$10$KOrCHvEB5CM7D.P3VcX2z.pSSBZKZhPqHW/QKym6V1S6fiG..xtBq',
|
||||
id: 'chatReceiver',
|
||||
locale: 'en',
|
||||
name: 'chatReceiver',
|
||||
role: 'user',
|
||||
showShoutsPublicly: false,
|
||||
slug: 'chatreceiver',
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
updatedAt: '2025-04-30T00:16:49.716Z',
|
||||
}
|
||||
|
||||
describe('sendChatMessageMail', () => {
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
recipientUser.locale = 'en'
|
||||
})
|
||||
|
||||
it('chat_message template', async () => {
|
||||
await expect(
|
||||
sendChatMessageMail({
|
||||
email: 'user@example.org',
|
||||
senderUser,
|
||||
recipientUser,
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
recipientUser.locale = 'de'
|
||||
})
|
||||
|
||||
it('chat_message template', async () => {
|
||||
await expect(
|
||||
sendChatMessageMail({
|
||||
email: 'user@example.org',
|
||||
senderUser,
|
||||
recipientUser,
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
204
backend/src/emails/sendEmail.ts
Normal file
204
backend/src/emails/sendEmail.ts
Normal file
@ -0,0 +1,204 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import path from 'node:path'
|
||||
|
||||
import Email from 'email-templates'
|
||||
import { createTransport } from 'nodemailer'
|
||||
// import type Email as EmailType from '@types/email-templates'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import logosWebapp from '@config/logos'
|
||||
import metadata from '@config/metadata'
|
||||
import { UserDbProperties } from '@db/types/User'
|
||||
|
||||
const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
|
||||
const hasDKIMData =
|
||||
CONFIG.SMTP_DKIM_DOMAINNAME && CONFIG.SMTP_DKIM_KEYSELECTOR && CONFIG.SMTP_DKIM_PRIVATKEY
|
||||
|
||||
const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
|
||||
const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI)
|
||||
|
||||
const defaultParams = {
|
||||
welcomeImageUrl,
|
||||
APPLICATION_NAME: CONFIG.APPLICATION_NAME,
|
||||
ORGANIZATION_NAME: metadata.ORGANIZATION_NAME,
|
||||
ORGANIZATION_URL: CONFIG.ORGANIZATION_URL,
|
||||
supportUrl: CONFIG.SUPPORT_URL,
|
||||
settingsUrl,
|
||||
}
|
||||
|
||||
export const transport = 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
|
||||
pool: true,
|
||||
maxConnections: CONFIG.SMTP_MAX_CONNECTIONS,
|
||||
maxMessages: CONFIG.SMTP_MAX_MESSAGES,
|
||||
auth: hasAuthData && {
|
||||
user: CONFIG.SMTP_USERNAME,
|
||||
pass: CONFIG.SMTP_PASSWORD,
|
||||
},
|
||||
dkim: hasDKIMData && {
|
||||
domainName: CONFIG.SMTP_DKIM_DOMAINNAME,
|
||||
keySelector: CONFIG.SMTP_DKIM_KEYSELECTOR,
|
||||
privateKey: CONFIG.SMTP_DKIM_PRIVATKEY,
|
||||
},
|
||||
})
|
||||
|
||||
const email = new Email({
|
||||
message: {
|
||||
from: `${CONFIG.APPLICATION_NAME}`,
|
||||
},
|
||||
transport,
|
||||
i18n: {
|
||||
locales: ['en', 'de'],
|
||||
defaultLocale: 'en',
|
||||
retryInDefaultLocale: false,
|
||||
directory: path.join(__dirname, 'locales'),
|
||||
updateFiles: false,
|
||||
objectNotation: true,
|
||||
mustacheConfig: {
|
||||
tags: ['{', '}'],
|
||||
disable: false,
|
||||
},
|
||||
},
|
||||
send: CONFIG.SEND_MAIL,
|
||||
preview: false,
|
||||
// This is very useful to see the emails sent by the unit tests
|
||||
/*
|
||||
preview: {
|
||||
open: {
|
||||
app: 'brave-browser',
|
||||
},
|
||||
},
|
||||
*/
|
||||
})
|
||||
|
||||
interface OriginalMessage {
|
||||
to: string
|
||||
from: string
|
||||
attachments: string[]
|
||||
subject: string
|
||||
html: string
|
||||
text: string
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const sendNotificationMail = async (notification: any): Promise<OriginalMessage> => {
|
||||
const locale = notification?.to?.locale
|
||||
const to = notification?.email
|
||||
const name = notification?.to?.name
|
||||
const template = notification?.reason
|
||||
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', template),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale,
|
||||
name,
|
||||
postTitle:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? notification?.from?.post?.title
|
||||
: notification?.from?.title,
|
||||
postUrl: new URL(
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? `/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}`
|
||||
: `/post/${notification?.from?.id}/${notification?.from?.slug}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
),
|
||||
postAuthorName:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? undefined
|
||||
: notification?.from?.author?.name,
|
||||
postAuthorUrl:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? undefined
|
||||
: new URL(
|
||||
`user/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
),
|
||||
commenterName:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? notification?.from?.author?.name
|
||||
: undefined,
|
||||
commenterUrl:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? new URL(
|
||||
`/user/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
)
|
||||
: undefined,
|
||||
commentUrl:
|
||||
notification?.from?.__typename === 'Comment'
|
||||
? new URL(
|
||||
`/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}#commentId-${notification?.from?.id}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
)
|
||||
: undefined,
|
||||
// chattingUser: 'SR-71',
|
||||
// chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
|
||||
groupUrl:
|
||||
notification?.from?.__typename === 'Group'
|
||||
? new URL(
|
||||
`/group/${notification?.from?.id}/${notification?.from?.slug}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
)
|
||||
: undefined,
|
||||
groupName:
|
||||
notification?.from?.__typename === 'Group' ? notification?.from?.name : undefined,
|
||||
groupRelatedUserName:
|
||||
notification?.from?.__typename === 'Group' ? notification?.relatedUser?.name : undefined,
|
||||
groupRelatedUserUrl:
|
||||
notification?.from?.__typename === 'Group'
|
||||
? new URL(
|
||||
`/user/${notification?.relatedUser?.id}/${notification?.relatedUser?.slug}`,
|
||||
CONFIG.CLIENT_URI,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChatMessageEmailInput {
|
||||
senderUser: UserDbProperties
|
||||
recipientUser: UserDbProperties
|
||||
email: string
|
||||
}
|
||||
|
||||
export const sendChatMessageMail = async (
|
||||
data: ChatMessageEmailInput,
|
||||
): Promise<OriginalMessage> => {
|
||||
const { senderUser, recipientUser } = data
|
||||
const to = data.email
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', 'chat_message'),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale: recipientUser.locale,
|
||||
name: recipientUser.name,
|
||||
chattingUser: senderUser.name,
|
||||
chattingUserUrl: new URL(`/user/${senderUser.id}/${senderUser.slug}`, CONFIG.CLIENT_URI),
|
||||
chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
475
backend/src/emails/sendNotificationMail.spec.ts
Normal file
475
backend/src/emails/sendNotificationMail.spec.ts
Normal file
@ -0,0 +1,475 @@
|
||||
import { sendNotificationMail } from './sendEmail'
|
||||
|
||||
describe('sendNotificationMail', () => {
|
||||
let locale = 'en'
|
||||
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
locale = 'en'
|
||||
})
|
||||
|
||||
it('followed_user_posted template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'followed_user_posted',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('post_in_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'post_in_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('mentioned_in_post template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'mentioned_in_post',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('commented_on_post template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'commented_on_post',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c1',
|
||||
slug: 'new-comment',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
post: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('mentioned_in_comment template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'mentioned_in_comment',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c1',
|
||||
slug: 'new-comment',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
post: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('changed_group_member_role template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'changed_group_member_role',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('user_joined_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'user_joined_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('user_left_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'user_left_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('removed_user_from_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'removed_user_from_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
locale = 'de'
|
||||
})
|
||||
|
||||
it('followed_user_posted template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'followed_user_posted',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('post_in_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'post_in_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('mentioned_in_post template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'mentioned_in_post',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('commented_on_post template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'commented_on_post',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c1',
|
||||
slug: 'new-comment',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
post: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('mentioned_in_comment template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'mentioned_in_comment',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c1',
|
||||
slug: 'new-comment',
|
||||
author: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
post: {
|
||||
id: 'p1',
|
||||
slug: 'new-post',
|
||||
title: 'New Post',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('changed_group_member_role template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'changed_group_member_role',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('user_joined_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'user_joined_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('user_left_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'user_left_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'u2',
|
||||
name: 'Peter Lustig',
|
||||
slug: 'peter-lustig',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('removed_user_from_group template', async () => {
|
||||
await expect(
|
||||
sendNotificationMail({
|
||||
reason: 'removed_user_from_group',
|
||||
email: 'user@example.org',
|
||||
to: {
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u1',
|
||||
slug: 'jenny-rostock',
|
||||
locale,
|
||||
},
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'g1',
|
||||
slug: 'the-group',
|
||||
name: 'The Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,7 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
- var groupUrl = groupUrl
|
||||
p= t('changedGroupMemberRole', { groupName })
|
||||
a.button(href=groupUrl)= t('buttons.viewGroup')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.changedGroupMemberRole')}`
|
||||
8
backend/src/emails/templates/chat_message/html.pug
Normal file
8
backend/src/emails/templates/chat_message/html.pug
Normal file
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('chatMessageStart')
|
||||
a.user(href=chattingUserUrl)= chattingUser
|
||||
= t('chatMessageEnd')
|
||||
a.button(href=chatUrl)= t('buttons.viewChat')
|
||||
1
backend/src/emails/templates/chat_message/subject.pug
Normal file
1
backend/src/emails/templates/chat_message/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.chatMessage')}`
|
||||
8
backend/src/emails/templates/commented_on_post/html.pug
Normal file
8
backend/src/emails/templates/commented_on_post/html.pug
Normal file
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=commenterUrl)= commenterName
|
||||
= t('commentedOnPost', { postTitle})
|
||||
a.button(href=commentUrl)= t('buttons.viewComment')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.commentedOnPost')}`
|
||||
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=postAuthorUrl)= postAuthorName
|
||||
= t('followedUserPosted', { postTitle })
|
||||
a.button(href=postUrl)= t('buttons.viewPost')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.followedUserPosted')}`
|
||||
5
backend/src/emails/templates/includes/footer.pug
Normal file
5
backend/src/emails/templates/includes/footer.pug
Normal file
@ -0,0 +1,5 @@
|
||||
footer
|
||||
.footer
|
||||
- var organizationUrl = ORGANIZATION_URL
|
||||
- var organizationName = ORGANIZATION_NAME
|
||||
a(href=organizationUrl)= organizationName
|
||||
14
backend/src/emails/templates/includes/greeting.pug
Normal file
14
backend/src/emails/templates/includes/greeting.pug
Normal file
@ -0,0 +1,14 @@
|
||||
//- This sets the greeting at the end of every e-mail
|
||||
.text-block
|
||||
- var organizationUrl = ORGANIZATION_URL
|
||||
- var team = APPLICATION_NAME
|
||||
- var settingsUrl = settingsUrl
|
||||
p= t('general.seeYou')
|
||||
a.organization(href=organizationUrl)= team
|
||||
| !
|
||||
p= t('general.yourTeam', { team })
|
||||
br
|
||||
p= t('general.settingsHint')
|
||||
a.settings(href=settingsUrl)= t('general.settingsName')
|
||||
| !
|
||||
|
||||
9
backend/src/emails/templates/includes/header.pug
Normal file
9
backend/src/emails/templates/includes/header.pug
Normal file
@ -0,0 +1,9 @@
|
||||
header
|
||||
.head
|
||||
- var img = welcomeImageUrl
|
||||
img.head-logo(
|
||||
alt="Welcome Image"
|
||||
loading="lazy"
|
||||
src=img
|
||||
)
|
||||
|
||||
1
backend/src/emails/templates/includes/salutation.pug
Normal file
1
backend/src/emails/templates/includes/salutation.pug
Normal file
@ -0,0 +1 @@
|
||||
h2= `${t('general.greeting')} ${name},`
|
||||
65
backend/src/emails/templates/includes/webflow.css
Normal file
65
backend/src/emails/templates/includes/webflow.css
Normal file
@ -0,0 +1,65 @@
|
||||
body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
26
backend/src/emails/templates/layout.pug
Normal file
26
backend/src/emails/templates/layout.pug
Normal file
@ -0,0 +1,26 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
meta(
|
||||
content="multipart/html; charset=UTF-8"
|
||||
http-equiv="content-type"
|
||||
)
|
||||
meta(
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
)
|
||||
style.
|
||||
.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}
|
||||
style
|
||||
include includes/webflow.css
|
||||
|
||||
body
|
||||
div.container
|
||||
include includes/header.pug
|
||||
include includes/salutation.pug
|
||||
|
||||
.wrapper
|
||||
block content
|
||||
include includes/greeting.pug
|
||||
|
||||
include includes/footer.pug
|
||||
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=commenterUrl)= commenterName
|
||||
= t('mentionedInComment', { postTitle})
|
||||
a.button(href=commentUrl)= t('buttons.viewComment')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.mentionedInComment')}`
|
||||
8
backend/src/emails/templates/mentioned_in_post/html.pug
Normal file
8
backend/src/emails/templates/mentioned_in_post/html.pug
Normal file
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=postAuthorUrl)= postAuthorName
|
||||
= t('mentionedInPost', { postTitle })
|
||||
a.button(href=postUrl)= t('buttons.viewPost')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.mentionedInPost')}`
|
||||
7
backend/src/emails/templates/post_in_group/html.pug
Normal file
7
backend/src/emails/templates/post_in_group/html.pug
Normal file
@ -0,0 +1,7 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
- var postUrl = postUrl
|
||||
p= t('postInGroup', { postTitle})
|
||||
a.button(href=postUrl)= t('buttons.viewPost')
|
||||
1
backend/src/emails/templates/post_in_group/subject.pug
Normal file
1
backend/src/emails/templates/post_in_group/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.postInGroup')}`
|
||||
@ -0,0 +1,5 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('removedUserFromGroup', { groupName })
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.removedUserFromGroup')}`
|
||||
8
backend/src/emails/templates/user_joined_group/html.pug
Normal file
8
backend/src/emails/templates/user_joined_group/html.pug
Normal file
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=groupRelatedUserUrl)= groupRelatedUserName
|
||||
= t('userJoinedGroup', { groupName })
|
||||
a.button(href=groupUrl)= t('buttons.viewGroup')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.userJoinedGroup')}`
|
||||
8
backend/src/emails/templates/user_left_group/html.pug
Normal file
8
backend/src/emails/templates/user_left_group/html.pug
Normal file
@ -0,0 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p
|
||||
a.user(href=groupRelatedUserUrl)= groupRelatedUserName
|
||||
= t('userLeftGroup', { groupName })
|
||||
a.button(href=groupUrl)= t('buttons.viewGroup')
|
||||
1
backend/src/emails/templates/user_left_group/subject.pug
Normal file
1
backend/src/emails/templates/user_left_group/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.userLeftGroup')}`
|
||||
@ -16,9 +16,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser, emaillessMember
|
||||
@ -208,7 +208,13 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'mentioned_in_post',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('sends 3 notifications', async () => {
|
||||
@ -280,7 +286,13 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'followed_user_posted',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('sends 3 notifications', async () => {
|
||||
@ -353,7 +365,13 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'post_in_group',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('sends 3 notifications', async () => {
|
||||
@ -427,7 +445,7 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends 3 notifications', async () => {
|
||||
@ -521,7 +539,13 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'mentioned_in_comment',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('sends 2 notifications', async () => {
|
||||
@ -603,7 +627,13 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'mentioned_in_comment',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('sends 2 notifications', async () => {
|
||||
@ -686,7 +716,7 @@ describe('emails sent for notifications', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends 2 notifications', async () => {
|
||||
|
||||
@ -14,9 +14,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
@ -268,17 +268,17 @@ describe('following users notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only two emails, as second follower has emails disabled and email-less follower has no email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
html: expect.stringContaining('Hello First Follower'),
|
||||
to: 'first-follower@example.org',
|
||||
email: 'first-follower@example.org',
|
||||
reason: 'followed_user_posted',
|
||||
}),
|
||||
)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
html: expect.stringContaining('Hello Third Follower'),
|
||||
to: 'third-follower@example.org',
|
||||
email: 'third-follower@example.org',
|
||||
reason: 'followed_user_posted',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -17,9 +17,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
@ -394,7 +394,25 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends only 3 emails, one for each user with an email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(3)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(3)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_post',
|
||||
}),
|
||||
)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'no.member@example.org',
|
||||
reason: 'mentioned_in_post',
|
||||
}),
|
||||
)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'pending.member@example.org',
|
||||
reason: 'mentioned_in_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -490,7 +508,13 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends only 1 email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -586,7 +610,13 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends only 1 email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -670,7 +700,19 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends 2 emails', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(3)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(3)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_comment',
|
||||
}),
|
||||
)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'no.member@example.org',
|
||||
reason: 'mentioned_in_comment',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -761,7 +803,13 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends 1 email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_comment',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -852,7 +900,13 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
|
||||
it('sends 1 email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: 'group.member@example.org',
|
||||
reason: 'mentioned_in_comment',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,9 +14,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
@ -213,10 +213,11 @@ describe('notifications for users that observe a post', () => {
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'post-author@example.org',
|
||||
email: 'post-author@example.org',
|
||||
reason: 'commented_on_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -303,15 +304,17 @@ describe('notifications for users that observe a post', () => {
|
||||
})
|
||||
|
||||
it('sends two emails', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'post-author@example.org',
|
||||
email: 'post-author@example.org',
|
||||
reason: 'commented_on_post',
|
||||
}),
|
||||
)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'first-commenter@example.org',
|
||||
email: 'first-commenter@example.org',
|
||||
reason: 'commented_on_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -417,10 +420,11 @@ describe('notifications for users that observe a post', () => {
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'second-commenter@example.org',
|
||||
email: 'second-commenter@example.org',
|
||||
reason: 'commented_on_post',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -12,9 +12,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
@ -109,7 +109,7 @@ describe('online status and sending emails', () => {
|
||||
})
|
||||
|
||||
it('sends NO email to the other user', () => {
|
||||
expect(sendMailMock).not.toBeCalled()
|
||||
expect(sendNotificationMailMock).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -135,7 +135,7 @@ describe('online status and sending emails', () => {
|
||||
})
|
||||
|
||||
it('sends email to the other user', () => {
|
||||
expect(sendMailMock).toBeCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -17,9 +17,9 @@ import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
@ -137,7 +137,7 @@ describe('notify group members of new posts in group', () => {
|
||||
slug: 'group-member',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
email: 'group.member@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
@ -295,7 +295,13 @@ describe('notify group members of new posts in group', () => {
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'post_in_group',
|
||||
email: 'group.member@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('group member mutes group', () => {
|
||||
@ -337,7 +343,7 @@ describe('notify group members of new posts in group', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('group member unmutes group again but disables email', () => {
|
||||
@ -392,7 +398,7 @@ describe('notify group members of new posts in group', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -433,7 +439,7 @@ describe('notify group members of new posts in group', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -473,7 +479,7 @@ describe('notify group members of new posts in group', () => {
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -20,16 +20,11 @@ import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
|
||||
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
const chatMessageTemplateMock = jest.fn()
|
||||
const notificationTemplateMock = jest.fn()
|
||||
jest.mock('../helpers/email/templateBuilder', () => ({
|
||||
chatMessageTemplate: () => chatMessageTemplateMock(),
|
||||
notificationTemplate: () => notificationTemplateMock(),
|
||||
const sendChatMessageMailMock: (notification) => void = jest.fn()
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendChatMessageMail: (notification) => sendChatMessageMailMock(notification),
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let isUserOnlineMock = jest.fn()
|
||||
@ -240,8 +235,13 @@ describe('notifications', () => {
|
||||
)
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'commented_on_post',
|
||||
email: 'test@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if I have disabled `emailNotificationsCommentOnObservedPost`', () => {
|
||||
@ -276,8 +276,7 @@ describe('notifications', () => {
|
||||
)
|
||||
|
||||
// No Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -398,8 +397,13 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'mentioned_in_post',
|
||||
email: 'test@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if I have disabled `emailNotificationsMention`', () => {
|
||||
@ -434,8 +438,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -941,8 +944,7 @@ describe('notifications', () => {
|
||||
userId: 'chatReceiver',
|
||||
})
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendChatMessageMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -977,8 +979,20 @@ describe('notifications', () => {
|
||||
userId: 'chatReceiver',
|
||||
})
|
||||
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(chatMessageTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendChatMessageMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendChatMessageMailMock).toHaveBeenCalledWith({
|
||||
email: 'user@example.org',
|
||||
senderUser: expect.objectContaining({
|
||||
name: 'chatSender',
|
||||
slug: 'chatsender',
|
||||
id: 'chatSender',
|
||||
}),
|
||||
recipientUser: expect.objectContaining({
|
||||
name: 'chatReceiver',
|
||||
slug: 'chatreceiver',
|
||||
id: 'chatReceiver',
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -998,8 +1012,7 @@ describe('notifications', () => {
|
||||
expect(pubsubSpy).not.toHaveBeenCalled()
|
||||
expect(pubsubSpy).not.toHaveBeenCalled()
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendChatMessageMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1019,8 +1032,7 @@ describe('notifications', () => {
|
||||
expect(pubsubSpy).not.toHaveBeenCalled()
|
||||
expect(pubsubSpy).not.toHaveBeenCalled()
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendChatMessageMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1056,8 +1068,7 @@ describe('notifications', () => {
|
||||
userId: 'chatReceiver',
|
||||
})
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendChatMessageMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1137,8 +1148,13 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'user_joined_group',
|
||||
email: 'owner@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberJoined`', () => {
|
||||
@ -1170,8 +1186,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1240,8 +1255,19 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'user_joined_group',
|
||||
email: 'owner@example.org',
|
||||
}),
|
||||
)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'user_left_group',
|
||||
email: 'owner@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberLeft`', () => {
|
||||
@ -1285,8 +1311,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1345,8 +1370,13 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'changed_group_member_role',
|
||||
email: 'test@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if the group member has disabled `emailNotificationsGroupMemberRoleChanged`', () => {
|
||||
@ -1378,8 +1408,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1437,8 +1466,13 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendNotificationMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: 'removed_user_from_group',
|
||||
email: 'test@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if the previous group member has disabled `emailNotificationsGroupMemberRemoved`', () => {
|
||||
@ -1470,8 +1504,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
expect(sendNotificationMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -10,13 +10,9 @@ import {
|
||||
CHAT_MESSAGE_ADDED,
|
||||
} from '@constants/subscriptions'
|
||||
import { getUnreadRoomsCount } from '@graphql/resolvers/rooms'
|
||||
import { sendMail } from '@middleware/helpers/email/sendMail'
|
||||
import {
|
||||
chatMessageTemplate,
|
||||
notificationTemplate,
|
||||
} from '@middleware/helpers/email/templateBuilder'
|
||||
import { isUserOnline } from '@middleware/helpers/isUserOnline'
|
||||
import { validateNotifyUsers } from '@middleware/validation/validationMiddleware'
|
||||
import { sendNotificationMail, sendChatMessageMail } from '@src/emails/sendEmail'
|
||||
|
||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||
|
||||
@ -35,12 +31,7 @@ const publishNotifications = async (
|
||||
!isUserOnline(notificationAdded.to) &&
|
||||
!emailsSent.includes(notificationAdded.email)
|
||||
) {
|
||||
sendMail(
|
||||
notificationTemplate({
|
||||
email: notificationAdded.email,
|
||||
variables: { notification: notificationAdded },
|
||||
}),
|
||||
)
|
||||
void sendNotificationMail(notificationAdded)
|
||||
emailsSent.push(notificationAdded.email)
|
||||
}
|
||||
})
|
||||
@ -496,7 +487,7 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
||||
|
||||
// Send EMail if we found a user(not blocked) and he is not considered online
|
||||
if (recipientUser.emailNotificationsChatMessage !== false && !isUserOnline(recipientUser)) {
|
||||
void sendMail(chatMessageTemplate({ email, variables: { senderUser, recipientUser } }))
|
||||
void sendChatMessageMail({ email, senderUser, recipientUser })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1043
backend/yarn.lock
1043
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user