From df4c603dea022146476812cbbc2b9f8f1e5e4870 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Tue, 5 May 2020 20:22:22 +0200 Subject: Switch emails to pug templates and provide richer html/text-only versions --- server/controllers/api/videos/abuse.ts | 20 +- server/lib/activitypub/process/process-flag.ts | 17 +- server/lib/emailer.ts | 392 +++++++++++---------- server/lib/emails/common/base.pug | 267 ++++++++++++++ server/lib/emails/common/greetings.pug | 11 + server/lib/emails/common/html.pug | 4 + server/lib/emails/common/mixins.pug | 3 + server/lib/emails/contact-form/html.pug | 9 + server/lib/emails/follower-on-channel/html.pug | 9 + server/lib/emails/password-create/html.pug | 10 + server/lib/emails/password-reset/html.pug | 12 + server/lib/emails/user-registered/html.pug | 10 + server/lib/emails/verify-email/html.pug | 14 + server/lib/emails/video-abuse-new/html.pug | 18 + .../lib/emails/video-auto-blacklist-new/html.pug | 17 + server/lib/emails/video-comment-mention/html.pug | 11 + server/lib/emails/video-comment-new/html.pug | 11 + server/lib/notifier.ts | 22 +- server/tests/api/server/contact-form.ts | 2 +- 19 files changed, 652 insertions(+), 207 deletions(-) create mode 100644 server/lib/emails/common/base.pug create mode 100644 server/lib/emails/common/greetings.pug create mode 100644 server/lib/emails/common/html.pug create mode 100644 server/lib/emails/common/mixins.pug create mode 100644 server/lib/emails/contact-form/html.pug create mode 100644 server/lib/emails/follower-on-channel/html.pug create mode 100644 server/lib/emails/password-create/html.pug create mode 100644 server/lib/emails/password-reset/html.pug create mode 100644 server/lib/emails/user-registered/html.pug create mode 100644 server/lib/emails/verify-email/html.pug create mode 100644 server/lib/emails/video-abuse-new/html.pug create mode 100644 server/lib/emails/video-auto-blacklist-new/html.pug create mode 100644 server/lib/emails/video-comment-mention/html.pug create mode 100644 server/lib/emails/video-comment-new/html.pug (limited to 'server') diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index bce50aefb..ec28fce67 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -1,5 +1,5 @@ import * as express from 'express' -import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' +import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse } from '../../../../shared' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' import { sequelizeTypescript } from '../../../initializers/database' @@ -24,6 +24,7 @@ import { Notifier } from '../../../lib/notifier' import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' import { MVideoAbuseAccountVideo } from '../../../typings/models/video' import { getServerActor } from '@server/models/application/application' +import { MAccountDefault } from '@server/typings/models' const auditLogger = auditLoggerFactory('abuse') const abuseVideoRouter = express.Router() @@ -117,9 +118,11 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) { async function reportVideoAbuse (req: express.Request, res: express.Response) { const videoInstance = res.locals.videoAll const body: VideoAbuseCreate = req.body + let reporterAccount: MAccountDefault + let videoAbuseJSON: VideoAbuse - const videoAbuse = await sequelizeTypescript.transaction(async t => { - const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) + const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { + reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) const abuseToCreate = { reporterAccountId: reporterAccount.id, @@ -137,14 +140,19 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) } - auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) + videoAbuseJSON = videoAbuseInstance.toFormattedJSON() + auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON)) return videoAbuseInstance }) - Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) + Notifier.Instance.notifyOnNewVideoAbuse({ + videoAbuse: videoAbuseJSON, + videoAbuseInstance, + reporter: reporterAccount.Actor.getIdentifier() + }) logger.info('Abuse report for video %s created.', videoInstance.name) - return res.json({ videoAbuse: videoAbuse.toFormattedJSON() }).end() + return res.json({ videoAbuseJSON }).end() } diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 9a488a473..7337f337c 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts @@ -8,7 +8,8 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { Notifier } from '../../notifier' import { getAPId } from '../../../helpers/activitypub' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models' +import { MActorSignature, MVideoAbuseAccountVideo } from '../../../typings/models' +import { AccountModel } from '@server/models/account/account' async function processFlagActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -36,8 +37,9 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, logger.debug('Reporting remote abuse for video %s.', getAPId(object)) const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) + const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t)) - const videoAbuse = await sequelizeTypescript.transaction(async t => { + const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { const videoAbuseData = { reporterAccountId: account.id, reason: flag.content, @@ -45,15 +47,22 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, state: VideoAbuseState.PENDING } - const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo + const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) videoAbuseInstance.Video = video + videoAbuseInstance.Account = reporterAccount logger.info('Remote abuse for video uuid %s created', flag.object) return videoAbuseInstance }) - Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) + const videoAbuseJSON = videoAbuseInstance.toFormattedJSON() + + Notifier.Instance.notifyOnNewVideoAbuse({ + videoAbuse: videoAbuseJSON, + videoAbuseInstance, + reporter: reporterAccount.Actor.getIdentifier() + }) } catch (err) { logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) } diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 45d57fd28..935c9e882 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts @@ -1,5 +1,5 @@ import { createTransport, Transporter } from 'nodemailer' -import { isTestInstance } from '../helpers/core-utils' +import { isTestInstance, root } from '../helpers/core-utils' import { bunyanLogger, logger } from '../helpers/logger' import { CONFIG, isEmailEnabled } from '../initializers/config' import { JobQueue } from './job-queue' @@ -16,6 +16,12 @@ import { import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' import { EmailPayload } from '@shared/models' +import { join } from 'path' +import { VideoAbuse } from '../../shared/models/videos' +import { SendEmailOptions } from '../../shared/models/server/emailer.model' +import { merge } from 'lodash' +import { VideoChannelModel } from '@server/models/video/video-channel' +const Email = require('email-templates') class Emailer { @@ -105,37 +111,36 @@ class Emailer { const channelName = video.VideoChannel.getDisplayName() const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() - const text = 'Hi dear user,\n\n' + - `Your subscription ${channelName} just published a new video: ${video.name}` + - '\n\n' + - `You can view it on ${videoUrl} ` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video', - text + subject: channelName + ' just published a new video', + text: `Your subscription ${channelName} just published a new video: "${video.name}".`, + locals: { + title: 'New content ', + action: { + text: 'View video', + url: videoUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { - const followerName = actorFollow.ActorFollower.Account.getDisplayName() const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() - const text = 'Hi dear user,\n\n' + - `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'follower-on-channel', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName, - text + subject: `New follower on your channel ${followingName}`, + locals: { + followerName: actorFollow.ActorFollower.Account.getDisplayName(), + followerUrl: actorFollow.ActorFollower.url, + followingName, + followingUrl: actorFollow.ActorFollowing.url, + followType + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -144,32 +149,28 @@ class Emailer { addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' - const text = 'Hi dear admin,\n\n' + - `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower', - text + subject: 'New instance follower', + text: `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}.`, + locals: { + title: 'New instance follower', + action: { + text: 'Review followers', + url: WEBSERVER.URL + '/admin/follows/followers-list' + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { - const text = 'Hi dear admin,\n\n' + - `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - + const instanceUrl = actorFollow.ActorFollowing.url const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following', - text + subject: 'Auto instance following', + text: `Your instance automatically followed a new instance: ${instanceUrl}.` } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -178,18 +179,17 @@ class Emailer { myVideoPublishedNotification (to: string[], video: MVideo) { const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() - const text = 'Hi dear user,\n\n' + - `Your video ${video.name} has been published.` + - '\n\n' + - `You can view it on ${videoUrl} ` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`, - text + subject: `Your video ${video.name} has been published`, + text: `Your video "${video.name}" has been published.`, + locals: { + title: 'You video is live', + action: { + text: 'View video', + url: videoUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -198,18 +198,17 @@ class Emailer { myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() - const text = 'Hi dear user,\n\n' + - `Your video import ${videoImport.getTargetIdentifier()} is finished.` + - '\n\n' + - `You can view the imported video on ${videoUrl} ` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`, - text + subject: `Your video import ${videoImport.getTargetIdentifier()} is complete`, + text: `Your video "${videoImport.getTargetIdentifier()}" just finished importing.`, + locals: { + title: 'Import complete', + action: { + text: 'View video', + url: videoUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -218,40 +217,47 @@ class Emailer { myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { const importUrl = WEBSERVER.URL + '/my-account/video-imports' - const text = 'Hi dear user,\n\n' + - `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + - '\n\n' + - `See your videos import dashboard for more information: ${importUrl}` + + const text = + `Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` + '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` + `See your videos import dashboard for more information: ${importUrl}.` const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, - text + subject: `Your video import "${videoImport.getTargetIdentifier()}" encountered an error`, + text, + locals: { + title: 'Import failed', + action: { + text: 'Review imports', + url: importUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { - const accountName = comment.Account.getDisplayName() const video = comment.Video + const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() - const text = 'Hi dear user,\n\n' + - `A new comment has been posted by ${accountName} on your video ${video.name}` + - '\n\n' + - `You can view it on ${commentUrl} ` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'video-comment-new', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name, - text + subject: 'New comment on your video ' + video.name, + locals: { + accountName: comment.Account.getDisplayName(), + accountUrl: comment.Account.Actor.url, + comment, + video, + videoUrl, + action: { + text: 'View comment', + url: commentUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -260,75 +266,88 @@ class Emailer { addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { const accountName = comment.Account.getDisplayName() const video = comment.Video + const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() - const text = 'Hi dear user,\n\n' + - `${accountName} mentioned you on video ${video.name}` + - '\n\n' + - `You can view the comment on ${commentUrl} ` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'video-comment-mention', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name, - text + subject: 'Mention on video ' + video.name, + locals: { + comment, + video, + videoUrl, + accountName, + action: { + text: 'View comment', + url: commentUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { - const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() - - const text = 'Hi,\n\n' + - `${WEBSERVER.HOST} received an abuse for the following video: ${videoUrl}\n\n` + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` + addVideoAbuseModeratorsNotification (to: string[], parameters: { + videoAbuse: VideoAbuse + videoAbuseInstance: MVideoAbuseVideo + reporter: string + }) { + const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id + const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() const emailPayload: EmailPayload = { + template: 'video-abuse-new', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse', - text + subject: `New video abuse report from ${parameters.reporter}`, + locals: { + videoUrl, + videoAbuseUrl, + videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), + videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), + videoAbuse: parameters.videoAbuse, + reporter: parameters.reporter, + action: { + text: 'View report #' + parameters.videoAbuse.id, + url: videoAbuseUrl + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { + async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() - - const text = 'Hi,\n\n' + - 'A recently added video was auto-blacklisted and requires moderator review before publishing.' + - '\n\n' + - `You can view it and take appropriate action on ${videoUrl}` + - '\n\n' + - `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` + const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON() const emailPayload: EmailPayload = { + template: 'video-auto-blacklist-new', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review', - text + subject: 'A new video is pending moderation', + locals: { + channel, + videoUrl, + videoName: videoBlacklist.Video.name, + action: { + text: 'Review autoblacklist', + url: VIDEO_AUTO_BLACKLIST_URL + } + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addNewUserRegistrationNotification (to: string[], user: MUser) { - const text = 'Hi,\n\n' + - `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'user-registered', to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST, - text + subject: `a new user registered on ${WEBSERVER.HOST}: ${user.username}`, + locals: { + user + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -341,16 +360,13 @@ class Emailer { const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` - const text = 'Hi,\n\n' + - blockedString + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`, - text + subject: `Video ${videoName} blacklisted`, + text: blockedString, + locals: { + title: 'Your video was blacklisted' + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -359,66 +375,53 @@ class Emailer { addVideoUnblacklistNotification (to: string[], video: MVideo) { const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() - const text = 'Hi,\n\n' + - `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { to, - subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`, - text + subject: `Video ${video.name} unblacklisted`, + text: `Your video "${video.name}" (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.`, + locals: { + title: 'Your video was unblacklisted' + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { - const text = 'Hi dear user,\n\n' + - `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + - `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + - 'If you are not the person who initiated this request, please ignore this email.\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'password-reset', to: [ to ], - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password', - text + subject: 'Reset your account password', + locals: { + resetPasswordUrl + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) { - const text = 'Hi,\n\n' + - `Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` + - `Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - + addPasswordCreateEmailJob (username: string, to: string, createPasswordUrl: string) { const emailPayload: EmailPayload = { + template: 'password-create', to: [ to ], - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password', - text + subject: 'Create your account password', + locals: { + username, + createPasswordUrl + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addVerifyEmailJob (to: string, verifyEmailUrl: string) { - const text = 'Welcome to PeerTube,\n\n' + - `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + - `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + - 'If you are not the person who initiated this request, please ignore this email.\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` - const emailPayload: EmailPayload = { + template: 'verify-email', to: [ to ], - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email', - text + subject: `Verify your email on ${WEBSERVER.HOST}`, + locals: { + verifyEmailUrl + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -427,39 +430,28 @@ class Emailer { addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { const reasonString = reason ? ` for the following reason: ${reason}` : '' const blockedWord = blocked ? 'blocked' : 'unblocked' - const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` - - const text = 'Hi,\n\n' + - blockedString + - '\n\n' + - 'Cheers,\n' + - `${CONFIG.EMAIL.BODY.SIGNATURE}` const to = user.email const emailPayload: EmailPayload = { to: [ to ], - subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord, - text + subject: 'Account ' + blockedWord, + text: `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) { - const text = 'Hello dear admin,\n\n' + - fromName + ' sent you a message' + - '\n\n---------------------------------------\n\n' + - body + - '\n\n---------------------------------------\n\n' + - 'Cheers,\n' + - 'PeerTube.' - const emailPayload: EmailPayload = { - fromDisplayName: fromEmail, - replyTo: fromEmail, + template: 'contact-form', to: [ CONFIG.ADMIN.EMAIL ], - subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject, - text + replyTo: `"${fromName}" <${fromEmail}>`, + subject: `(contact form) ${subject}`, + locals: { + fromName, + fromEmail, + body + } } return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) @@ -470,18 +462,44 @@ class Emailer { throw new Error('Cannot send mail because SMTP is not configured.') } - const fromDisplayName = options.fromDisplayName - ? options.fromDisplayName + const fromDisplayName = options.from + ? options.from : WEBSERVER.HOST + const email = new Email({ + send: true, + message: { + from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>` + }, + transport: this.transporter, + views: { + root: join(root(), 'server', 'lib', 'emails') + }, + subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX + }) + for (const to of options.to) { - await this.transporter.sendMail({ - from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`, - replyTo: options.replyTo, - to, - subject: options.subject, - text: options.text - }) + await email + .send(merge( + { + template: 'common', + message: { + to, + from: options.from, + subject: options.subject, + replyTo: options.replyTo + }, + locals: { // default variables available in all templates + WEBSERVER, + EMAIL: CONFIG.EMAIL, + text: options.text, + subject: options.subject + } + }, + options // overriden/new variables given for a specific template in the payload + ) as SendEmailOptions) + .then(logger.info) + .catch(logger.error) } } diff --git a/server/lib/emails/common/base.pug b/server/lib/emails/common/base.pug new file mode 100644 index 000000000..9a1894cab --- /dev/null +++ b/server/lib/emails/common/base.pug @@ -0,0 +1,267 @@ +//- + The email background color is defined in three places: + 1. body tag: for most email clients + 2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr + 3. mso conditional: For Windows 10 Mail +- var backgroundColor = "#fff"; +- var mainColor = "#f2690d"; +doctype html +head + // This template is heavily adapted from the Cerberus Fluid template. Kudos to them! + meta(charset='utf-8') + //- utf-8 works for most cases + meta(name='viewport' content='width=device-width') + //- Forcing initial-scale shouldn't be necessary + meta(http-equiv='X-UA-Compatible' content='IE=edge') + //- Use the latest (edge) version of IE rendering engine + meta(name='x-apple-disable-message-reformatting') + //- Disable auto-scale in iOS 10 Mail entirely + meta(name='format-detection' content='telephone=no,address=no,email=no,date=no,url=no') + //- Tell iOS not to automatically link certain text strings. + meta(name='color-scheme' content='light') + meta(name='supported-color-schemes' content='light') + //- The title tag shows in email notifications, like Android 4.4. + title #{subject} + //- What it does: Makes background images in 72ppi Outlook render at correct size. + //if gte mso 9 + xml + o:officedocumentsettings + o:allowpng + o:pixelsperinch 96 + //- CSS Reset : BEGIN + style. + /* What it does: Tells the email client that only light styles are provided but the client can transform them to dark. A duplicate of meta color-scheme meta tag above. */ + :root { + color-scheme: light; + supported-color-schemes: light; + } + /* What it does: Remove spaces around the email design added by some email clients. */ + /* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */ + html, + body { + margin: 0 auto !important; + padding: 0 !important; + height: 100% !important; + width: 100% !important; + } + /* What it does: Stops email clients resizing small text. */ + * { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + } + /* What it does: Centers email on Android 4.4 */ + div[style*="margin: 16px 0"] { + margin: 0 !important; + } + /* What it does: forces Samsung Android mail clients to use the entire viewport */ + #MessageViewBody, #MessageWebViewDiv{ + width: 100% !important; + } + /* What it does: Stops Outlook from adding extra spacing to tables. */ + table, + td { + mso-table-lspace: 0pt !important; + mso-table-rspace: 0pt !important; + } + /* What it does: Fixes webkit padding issue. */ + table { + border-spacing: 0 !important; + border-collapse: collapse !important; + table-layout: fixed !important; + margin: 0 auto !important; + } + /* What it does: Uses a better rendering method when resizing images in IE. */ + img { + -ms-interpolation-mode:bicubic; + } + /* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */ + a { + text-decoration: none; + } + a:not(.nocolor) { + color: #{mainColor}; + } + a.nocolor { + color: inherit !important; + } + /* What it does: A work-around for email clients meddling in triggered links. */ + a[x-apple-data-detectors], /* iOS */ + .unstyle-auto-detected-links a, + .aBn { + border-bottom: 0 !important; + cursor: default !important; + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } + /* What it does: Prevents Gmail from displaying a download button on large, non-linked images. */ + .a6S { + display: none !important; + opacity: 0.01 !important; + } + /* What it does: Prevents Gmail from changing the text color in conversation threads. */ + .im { + color: inherit !important; + } + /* If the above doesn't work, add a .g-img class to any image in question. */ + img.g-img + div { + display: none !important; + } + /* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */ + /* Create one of these media queries for each additional viewport size you'd like to fix */ + /* iPhone 4, 4S, 5, 5S, 5C, and 5SE */ + @media only screen and (min-device-width: 320px) and (max-device-width: 374px) { + u ~ div .email-container { + min-width: 320px !important; + } + } + /* iPhone 6, 6S, 7, 8, and X */ + @media only screen and (min-device-width: 375px) and (max-device-width: 413px) { + u ~ div .email-container { + min-width: 375px !important; + } + } + /* iPhone 6+, 7+, and 8+ */ + @media only screen and (min-device-width: 414px) { + u ~ div .email-container { + min-width: 414px !important; + } + } + //- CSS Reset : END + //- CSS for PeerTube : START + style. + blockquote { + margin-left: 0; + padding-left: 20px; + border-left: 2px solid #f2690d; + } + //- CSS for PeerTube : END + //- Progressive Enhancements : BEGIN + style. + /* What it does: Hover styles for buttons */ + .button-td, + .button-a { + transition: all 100ms ease-in; + } + .button-td-primary:hover, + .button-a-primary:hover { + background: #555555 !important; + border-color: #555555 !important; + } + /* Media Queries */ + @media screen and (max-width: 600px) { + /* What it does: Adjust typography on small screens to improve readability */ + .email-container p { + font-size: 17px !important; + } + } + //- Progressive Enhancements : END + +body(width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #{backgroundColor};") + center(role='article' aria-roledescription='email' lang='en' style='width: 100%; background-color: #{backgroundColor};') + //if mso | IE + table(role='presentation' border='0' cellpadding='0' cellspacing='0' width='100%' style='background-color: #fff;') + tr + td + //- Visually Hidden Preheader Text : BEGIN + div(style='max-height:0; overflow:hidden; mso-hide:all;' aria-hidden='true') + block preheader + //- Visually Hidden Preheader Text : END + + //- Create white space after the desired preview text so email clients don’t pull other distracting text into the inbox preview. Extend as necessary. + //- Preview Text Spacing Hack : BEGIN + div(style='display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;') + | ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ + //- Preview Text Spacing Hack : END + + //- + Set the email width. Defined in two places: + 1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 600px. + 2. MSO tags for Desktop Windows Outlook enforce a 600px width. + .email-container(style='max-width: 600px; margin: 0 auto;') + //if mso + table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='600') + tr + td + //- Email Body : BEGIN + table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;') + //- 1 Column Text + Button : BEGIN + tr + td(style='background-color: #ffffff;') + table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') + tr + td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;') + table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%") + tr + td(width="40px") + img(src=`${WEBSERVER.URL}/client/assets/images/icons/icon-192x192.png` width="auto" height="30px" alt="icon" border="0" style="height: 30px; background: #ffffff; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;") + td + h1(style='margin: 10px 0 10px 0; font-family: sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;') + block title + if title + | #{title} + else + | Something requires your attention + p(style='margin: 0;') + block body + if action + tr + td(style='padding: 0 20px;') + //- Button : BEGIN + table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' style='margin: auto;') + tr + td.button-td.button-td-primary(style='border-radius: 4px; background: #222222;') + a.button-a.button-a-primary(href=action.url style='background: #222222; border: 1px solid #000000; font-family: sans-serif; font-size: 15px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;') #{action.text} + //- Button : END + //- 1 Column Text + Button : END + //- Clear Spacer : BEGIN + tr + td(aria-hidden='true' height='20' style='font-size: 0px; line-height: 0px;') + br + //- Clear Spacer : END + //- 1 Column Text : BEGIN + if username + tr + td(style='background-color: #cccccc;') + table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') + tr + td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;') + p(style='margin: 0;') + | You are receiving this email as part of your notification settings on #{WEBSERVER.HOST} for your account #{username}. + //- 1 Column Text : END + //- Email Body : END + //- Email Footer : BEGIN + table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;') + tr + td(style='padding: 20px; padding-bottom: 0px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;') + webversion + a.nocolor(href=`${WEBSERVER.URL}/my-account/notifications` style='color: #cccccc; font-weight: bold;') View in your notifications + br + tr + td(style='padding: 20px; padding-top: 10px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;') + unsubscribe + a.nocolor(href=`${WEBSERVER.URL}/my-account/settings#notifications` style='color: #888888;') Manage your notification preferences in your profile + br + //- Email Footer : END + //if mso + //- Full Bleed Background Section : BEGIN + table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style=`background-color: ${mainColor};`) + tr + td + .email-container(align='center' style='max-width: 600px; margin: auto;') + //if mso + table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='600' align='center') + tr + td + table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') + tr + td(style='padding: 20px; text-align: left; font-family: sans-serif; font-size: 12px; line-height: 20px; color: #ffffff;') + table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%") + tr + td(valign="top") #[a(href="https://github.com/Chocobozzz/PeerTube" style="color: white !important") PeerTube © 2015-#{new Date().getFullYear()}] #[a(href="https://github.com/Chocobozzz/PeerTube/blob/master/CREDITS.md" style="color: white !important") PeerTube Contributors] + //if mso + //- Full Bleed Background Section : END + //if mso | IE diff --git a/server/lib/emails/common/greetings.pug b/server/lib/emails/common/greetings.pug new file mode 100644 index 000000000..5efe29dfb --- /dev/null +++ b/server/lib/emails/common/greetings.pug @@ -0,0 +1,11 @@ +extends base + +block body + if username + p Hi #{username}, + else + p Hi, + block content + p + | Cheers,#[br] + | #{EMAIL.BODY.SIGNATURE} \ No newline at end of file diff --git a/server/lib/emails/common/html.pug b/server/lib/emails/common/html.pug new file mode 100644 index 000000000..d76168b85 --- /dev/null +++ b/server/lib/emails/common/html.pug @@ -0,0 +1,4 @@ +extends greetings + +block content + p !{text} \ No newline at end of file diff --git a/server/lib/emails/common/mixins.pug b/server/lib/emails/common/mixins.pug new file mode 100644 index 000000000..76b805a24 --- /dev/null +++ b/server/lib/emails/common/mixins.pug @@ -0,0 +1,3 @@ +mixin channel(channel) + - var handle = `${channel.name}@${channel.host}` + | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] \ No newline at end of file diff --git a/server/lib/emails/contact-form/html.pug b/server/lib/emails/contact-form/html.pug new file mode 100644 index 000000000..0073ff78e --- /dev/null +++ b/server/lib/emails/contact-form/html.pug @@ -0,0 +1,9 @@ +extends ../common/greetings + +block title + | Someone just used the contact form + +block content + p #{fromName} sent you a message via the contact form on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]: + blockquote(style='white-space: pre-wrap') #{body} + p You can contact them at #[a(href=`mailto:${fromEmail}`) #{fromEmail}], or simply reply to this email to get in touch. \ No newline at end of file diff --git a/server/lib/emails/follower-on-channel/html.pug b/server/lib/emails/follower-on-channel/html.pug new file mode 100644 index 000000000..8a352e90f --- /dev/null +++ b/server/lib/emails/follower-on-channel/html.pug @@ -0,0 +1,9 @@ +extends ../common/greetings + +block title + | New follower on your channel + +block content + p. + Your #{followType} #[a(href=followingUrl) #{followingName}] has a new subscriber: + #[a(href=followerUrl) #{followerName}]. \ No newline at end of file diff --git a/server/lib/emails/password-create/html.pug b/server/lib/emails/password-create/html.pug new file mode 100644 index 000000000..45ff3078a --- /dev/null +++ b/server/lib/emails/password-create/html.pug @@ -0,0 +1,10 @@ +extends ../common/greetings + +block title + | Password creation for your account + +block content + p. + Welcome to #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your PeerTube instance. Your username is: #{username}. + Please set your password by following #[a(href=createPasswordUrl) this link]: #[a(href=createPasswordUrl) #{createPasswordUrl}] + (this link will expire within seven days). \ No newline at end of file diff --git a/server/lib/emails/password-reset/html.pug b/server/lib/emails/password-reset/html.pug new file mode 100644 index 000000000..bb6a9d16b --- /dev/null +++ b/server/lib/emails/password-reset/html.pug @@ -0,0 +1,12 @@ +extends ../common/greetings + +block title + | Password reset for your account + +block content + p. + A reset password procedure for your account ${to} has been requested on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]. + Please follow #[a(href=resetPasswordUrl) this link] to reset it: #[a(href=resetPasswordUrl) #{resetPasswordUrl}] + (the link will expire within 1 hour) + p. + If you are not the person who initiated this request, please ignore this email. \ No newline at end of file diff --git a/server/lib/emails/user-registered/html.pug b/server/lib/emails/user-registered/html.pug new file mode 100644 index 000000000..20f62125e --- /dev/null +++ b/server/lib/emails/user-registered/html.pug @@ -0,0 +1,10 @@ +extends ../common/greetings + +block title + | A new user registered + +block content + - var mail = user.email || user.pendingEmail; + p + | User #[a(href=`${WEBSERVER.URL}/accounts/${user.username}`) #{user.username}] just registered. + | You might want to contact them at #[a(href=`mailto:${mail}`) #{mail}]. \ No newline at end of file diff --git a/server/lib/emails/verify-email/html.pug b/server/lib/emails/verify-email/html.pug new file mode 100644 index 000000000..8a4a77703 --- /dev/null +++ b/server/lib/emails/verify-email/html.pug @@ -0,0 +1,14 @@ +extends ../common/greetings + +block title + | Account verification + +block content + p Welcome to PeerTube! + p. + You just created an account #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your new PeerTube instance. + Your username there is: #{username}. + p. + To start using PeerTube on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] you must verify your email first! + Please follow #[a(href=verifyEmailUrl) this link] to verify this email belongs to you: #[a(href=verifyEmailUrl) #{verifyEmailUrl}] + If you are not the person who initiated this request, please ignore this email. \ No newline at end of file diff --git a/server/lib/emails/video-abuse-new/html.pug b/server/lib/emails/video-abuse-new/html.pug new file mode 100644 index 000000000..999c89d26 --- /dev/null +++ b/server/lib/emails/video-abuse-new/html.pug @@ -0,0 +1,18 @@ +extends ../common/greetings +include ../common/mixins.pug + +block title + | A video is pending moderation + +block content + p + | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video " + a(href=videoUrl) #{videoAbuse.video.name} + | " by #[+channel(videoAbuse.video.channel)] + if videoPublishedAt + | , published the #{videoPublishedAt}. + else + | , uploaded the #{videoCreatedAt} but not yet published. + p The reporter, #{reporter}, cited the following reason(s): + blockquote #{videoAbuse.reason} + br(style="display: none;") diff --git a/server/lib/emails/video-auto-blacklist-new/html.pug b/server/lib/emails/video-auto-blacklist-new/html.pug new file mode 100644 index 000000000..07c8dfd16 --- /dev/null +++ b/server/lib/emails/video-auto-blacklist-new/html.pug @@ -0,0 +1,17 @@ +extends ../common/greetings +include ../common/mixins + +block title + | A video is pending moderation + +block content + p + | A recently added video was auto-blacklisted and requires moderator review before going public: + | + a(href=videoUrl) #{videoName} + | + | by #[+channel(channel)]. + p. + Apart from the publisher and the moderation team, no one will be able to see the video until you + unblacklist it. If you trust the publisher, any admin can whitelist the user for later videos so + that they don't require approval before going public. diff --git a/server/lib/emails/video-comment-mention/html.pug b/server/lib/emails/video-comment-mention/html.pug new file mode 100644 index 000000000..9e9ced62d --- /dev/null +++ b/server/lib/emails/video-comment-mention/html.pug @@ -0,0 +1,11 @@ +extends ../common/greetings + +block title + | Someone mentioned you + +block content + p. + #[a(href=accountUrl title=handle) #{accountName}] mentioned you in a comment on video + "#[a(href=videoUrl) #{video.name}]": + blockquote #{comment.text} + br(style="display: none;") \ No newline at end of file diff --git a/server/lib/emails/video-comment-new/html.pug b/server/lib/emails/video-comment-new/html.pug new file mode 100644 index 000000000..075af5717 --- /dev/null +++ b/server/lib/emails/video-comment-new/html.pug @@ -0,0 +1,11 @@ +extends ../common/greetings + +block title + | Someone commented your video + +block content + p. + #[a(href=accountUrl title=handle) #{accountName}] added a comment on your video + "#[a(href=videoUrl) #{video.name}]": + blockquote #{comment.text} + br(style="display: none;") \ No newline at end of file diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 710c2d30f..017739523 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts @@ -5,7 +5,7 @@ import { UserNotificationModel } from '../models/account/user-notification' import { UserModel } from '../models/account/user' import { PeerTubeSocket } from './peertube-socket' import { CONFIG } from '../initializers/config' -import { VideoPrivacy, VideoState } from '../../shared/models/videos' +import { VideoPrivacy, VideoState, VideoAbuse } from '../../shared/models/videos' import { AccountBlocklistModel } from '../models/account/account-blocklist' import { MCommentOwnerVideo, @@ -77,9 +77,9 @@ class Notifier { .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) } - notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { - this.notifyModeratorsOfNewVideoAbuse(videoAbuse) - .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) + notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void { + this.notifyModeratorsOfNewVideoAbuse(parameters) + .catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err })) } notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { @@ -350,11 +350,15 @@ class Notifier { return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) } - private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) { + private async notifyModeratorsOfNewVideoAbuse (parameters: { + videoAbuse: VideoAbuse + videoAbuseInstance: MVideoAbuseVideo + reporter: string + }) { const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) if (moderators.length === 0) return - logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) + logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url) function settingGetter (user: MUserWithNotificationSetting) { return user.NotificationSetting.videoAbuseAsModerator @@ -364,15 +368,15 @@ class Notifier { const notification: UserNotificationModelForApi = await UserNotificationModel.create({ type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, userId: user.id, - videoAbuseId: videoAbuse.id + videoAbuseId: parameters.videoAbuse.id }) - notification.VideoAbuse = videoAbuse + notification.VideoAbuse = parameters.videoAbuseInstance return notification } function emailSender (emails: string[]) { - return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse) + return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters) } return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts index bd1b0e38a..8d1270358 100644 --- a/server/tests/api/server/contact-form.ts +++ b/server/tests/api/server/contact-form.ts @@ -46,7 +46,7 @@ describe('Test contact form', function () { const email = emails[0] expect(email['from'][0]['address']).equal('test-admin@localhost') - expect(email['from'][0]['name']).equal('toto@example.com') + expect(email['replyTo'][0]['address']).equal('toto@example.com') expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com') expect(email['subject']).contains('my subject') expect(email['text']).contains('my super message') -- cgit v1.2.3