mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-06 03:50:26 +02:00
add filter:email.template-path.result / filter:email.subject.result (#6876)
* add filter:email.template-path.result / filter:email.subject.result closes #3392 * Remove juice * Kill mock server --------- Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
parent
e9f887323a
commit
75d7c2a9dc
7 changed files with 98 additions and 7 deletions
|
@ -224,6 +224,7 @@
|
||||||
"@types/multer": "^1.3.3",
|
"@types/multer": "^1.3.3",
|
||||||
"@types/node": "^18.13.0",
|
"@types/node": "^18.13.0",
|
||||||
"@types/nodemailer": "^6.2.0",
|
"@types/nodemailer": "^6.2.0",
|
||||||
|
"@types/pug": "^2.0.10",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@types/validator": "^13.9.0",
|
"@types/validator": "^13.9.0",
|
||||||
"@types/webtorrent": "^0.109.0",
|
"@types/webtorrent": "^0.109.0",
|
||||||
|
|
|
@ -145,7 +145,11 @@ export const serverFilterHookObject = {
|
||||||
// Peertube >= 7.1
|
// Peertube >= 7.1
|
||||||
'filter:oauth.password-grant.get-user.params': true,
|
'filter:oauth.password-grant.get-user.params': true,
|
||||||
'filter:api.email-verification.ask-send-verify-email.body': true,
|
'filter:api.email-verification.ask-send-verify-email.body': true,
|
||||||
'filter:api.users.ask-reset-password.body': true
|
'filter:api.users.ask-reset-password.body': true,
|
||||||
|
|
||||||
|
// Peertube >= 7.2
|
||||||
|
'filter:email.subject.result': true,
|
||||||
|
'filter:email.template-path.result': true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerFilterHookName = keyof typeof serverFilterHookObject
|
export type ServerFilterHookName = keyof typeof serverFilterHookObject
|
||||||
|
|
1
packages/tests/fixtures/peertube-plugin-test/emails/password-reset.pug
vendored
Normal file
1
packages/tests/fixtures/peertube-plugin-test/emails/password-reset.pug
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
p Custom password reset email
|
|
@ -1,3 +1,5 @@
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
|
async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
|
||||||
{
|
{
|
||||||
registerSetting({
|
registerSetting({
|
||||||
|
@ -445,6 +447,28 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
registerHook({
|
||||||
|
target: 'filter:email.template-path.result',
|
||||||
|
handler: (templatePath, { view }) => {
|
||||||
|
if (view === 'password-reset/html') {
|
||||||
|
return path.join(__dirname, 'emails', 'password-reset.pug')
|
||||||
|
}
|
||||||
|
|
||||||
|
return templatePath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
registerHook({
|
||||||
|
target: 'filter:email.subject.result',
|
||||||
|
handler: (subject, { template }) => {
|
||||||
|
if (template === 'password-reset') {
|
||||||
|
return 'Custom subject'
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Upload/import/live attributes
|
// Upload/import/live attributes
|
||||||
for (const target of [
|
for (const target of [
|
||||||
'filter:api.video.upload.video-attribute.result',
|
'filter:api.video.upload.video-attribute.result',
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
VideoPrivacy
|
VideoPrivacy
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
import {
|
import {
|
||||||
|
ConfigCommand,
|
||||||
PeerTubeServer,
|
PeerTubeServer,
|
||||||
PluginsCommand,
|
PluginsCommand,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
|
@ -26,6 +27,7 @@ import {
|
||||||
import { expectEndWith } from '@tests/shared/checks.js'
|
import { expectEndWith } from '@tests/shared/checks.js'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { FIXTURE_URLS } from '../shared/fixture-urls.js'
|
import { FIXTURE_URLS } from '../shared/fixture-urls.js'
|
||||||
|
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
|
||||||
|
|
||||||
describe('Test plugin filter hooks', function () {
|
describe('Test plugin filter hooks', function () {
|
||||||
let servers: PeerTubeServer[]
|
let servers: PeerTubeServer[]
|
||||||
|
@ -33,11 +35,13 @@ describe('Test plugin filter hooks', function () {
|
||||||
let threadId: number
|
let threadId: number
|
||||||
let videoPlaylistUUID: string
|
let videoPlaylistUUID: string
|
||||||
let importUserToken: string
|
let importUserToken: string
|
||||||
|
const emails: object[] = []
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
servers = await createMultipleServers(2)
|
const emailPort = await MockSmtpServer.Instance.collectEmails(emails)
|
||||||
|
servers = await createMultipleServers(2, ConfigCommand.getEmailOverrideConfig(emailPort))
|
||||||
await setAccessTokensToServers(servers)
|
await setAccessTokensToServers(servers)
|
||||||
await setDefaultVideoChannel(servers)
|
await setDefaultVideoChannel(servers)
|
||||||
await doubleFollow(servers[0], servers[1])
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
@ -931,7 +935,43 @@ describe('Test plugin filter hooks', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Emails', function () {
|
||||||
|
let server: PeerTubeServer
|
||||||
|
const emailAddress = 'plugin-admin@example.com'
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
server = servers[0]
|
||||||
|
await server.users.create({ username: 'plugin-admin', email: emailAddress })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should run filter:email.template-path.result', async function () {
|
||||||
|
const preEmailCount = emails.length
|
||||||
|
await server.users.askResetPassword({ email: emailAddress })
|
||||||
|
|
||||||
|
await waitJobs(server)
|
||||||
|
expect(emails).to.have.lengthOf(preEmailCount + 1)
|
||||||
|
|
||||||
|
const email = emails[preEmailCount]
|
||||||
|
|
||||||
|
expect(email['html']).to.contain('Custom password reset email')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should run filter:email.subject.result', async function () {
|
||||||
|
const preEmailCount = emails.length
|
||||||
|
await server.users.askResetPassword({ email: emailAddress })
|
||||||
|
|
||||||
|
await waitJobs(server)
|
||||||
|
expect(emails).to.have.lengthOf(preEmailCount + 1)
|
||||||
|
|
||||||
|
const email = emails[preEmailCount]
|
||||||
|
|
||||||
|
expect(email['subject']).to.contain('Custom subject')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
MockSmtpServer.Instance.kill()
|
||||||
|
|
||||||
await cleanupTests(servers)
|
await cleanupTests(servers)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,11 +6,13 @@ import { readFileSync } from 'fs'
|
||||||
import merge from 'lodash-es/merge.js'
|
import merge from 'lodash-es/merge.js'
|
||||||
import { Transporter, createTransport } from 'nodemailer'
|
import { Transporter, createTransport } from 'nodemailer'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import pug from 'pug'
|
||||||
import { bunyanLogger, logger } from '../helpers/logger.js'
|
import { bunyanLogger, logger } from '../helpers/logger.js'
|
||||||
import { CONFIG, isEmailEnabled } from '../initializers/config.js'
|
import { CONFIG, isEmailEnabled } from '../initializers/config.js'
|
||||||
import { WEBSERVER } from '../initializers/constants.js'
|
import { WEBSERVER } from '../initializers/constants.js'
|
||||||
import { MRegistration, MUser, MUserExport, MUserImport } from '../types/models/index.js'
|
import { MRegistration, MUser, MUserExport, MUserImport } from '../types/models/index.js'
|
||||||
import { JobQueue } from './job-queue/index.js'
|
import { JobQueue } from './job-queue/index.js'
|
||||||
|
import { Hooks } from './plugins/hooks.js'
|
||||||
|
|
||||||
class Emailer {
|
class Emailer {
|
||||||
|
|
||||||
|
@ -260,15 +262,29 @@ class Emailer {
|
||||||
{ selector: 'a', options: { hideLinkHrefIfSameAsText: true } }
|
{ selector: 'a', options: { hideLinkHrefIfSameAsText: true } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
render: async (view: string, locals: Record<string, string>) => {
|
||||||
|
if (view.split('/').pop() !== 'html') return undefined
|
||||||
|
|
||||||
|
const templatePath = await Hooks.wrapObject(
|
||||||
|
join(root(), 'dist', 'core', 'assets', 'email-templates', view + '.pug'),
|
||||||
|
'filter:email.template-path.result',
|
||||||
|
{ view }
|
||||||
|
)
|
||||||
|
const compiledTemplate = pug.compileFile(templatePath)
|
||||||
|
|
||||||
|
return compiledTemplate(locals)
|
||||||
|
},
|
||||||
message: {
|
message: {
|
||||||
from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`
|
from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`
|
||||||
},
|
},
|
||||||
transport: this.transporter,
|
transport: this.transporter,
|
||||||
views: {
|
|
||||||
root: join(root(), 'dist', 'core', 'assets', 'email-templates')
|
|
||||||
},
|
|
||||||
subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX
|
subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX
|
||||||
})
|
})
|
||||||
|
const subject = await Hooks.wrapObject(
|
||||||
|
options.subject,
|
||||||
|
'filter:email.subject.result',
|
||||||
|
{ template: 'template' in options ? options.template : undefined }
|
||||||
|
)
|
||||||
|
|
||||||
const toEmails = arrayify(options.to)
|
const toEmails = arrayify(options.to)
|
||||||
|
|
||||||
|
@ -280,7 +296,7 @@ class Emailer {
|
||||||
message: {
|
message: {
|
||||||
to,
|
to,
|
||||||
from: options.from,
|
from: options.from,
|
||||||
subject: options.subject,
|
subject,
|
||||||
replyTo: options.replyTo
|
replyTo: options.replyTo
|
||||||
},
|
},
|
||||||
locals: { // default variables available in all templates
|
locals: { // default variables available in all templates
|
||||||
|
@ -288,7 +304,7 @@ class Emailer {
|
||||||
EMAIL: CONFIG.EMAIL,
|
EMAIL: CONFIG.EMAIL,
|
||||||
instanceName: CONFIG.INSTANCE.NAME,
|
instanceName: CONFIG.INSTANCE.NAME,
|
||||||
text: options.text,
|
text: options.text,
|
||||||
subject: options.subject
|
subject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2915,6 +2915,11 @@
|
||||||
pg-protocol "*"
|
pg-protocol "*"
|
||||||
pg-types "^2.2.0"
|
pg-types "^2.2.0"
|
||||||
|
|
||||||
|
"@types/pug@^2.0.10":
|
||||||
|
version "2.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.10.tgz#52f8dbd6113517aef901db20b4f3fca543b88c1f"
|
||||||
|
integrity sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==
|
||||||
|
|
||||||
"@types/qs@*":
|
"@types/qs@*":
|
||||||
version "6.9.18"
|
version "6.9.18"
|
||||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue