1
0
Fork 0
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:
kontrollanten 2025-04-02 15:16:29 +02:00 committed by GitHub
parent e9f887323a
commit 75d7c2a9dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 98 additions and 7 deletions

View file

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

View file

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

View file

@ -0,0 +1 @@
p Custom password reset email

View file

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

View file

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

View file

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

View file

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