diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js index d735023f7c..f11edbd2e7 100644 --- a/packages/worker/src/api/controllers/admin/email.js +++ b/packages/worker/src/api/controllers/admin/email.js @@ -28,6 +28,12 @@ async function buildEmail(purpose, email, user) { getTemplateByPurpose(TYPE, EmailTemplatePurpose.STYLES), getTemplateByPurpose(TYPE, purpose), ]) + if (!base || !styles || !body) { + throw "Unable to build email, missing base components" + } + base = base.contents + styles = styles.contents + body = body.contents // TODO: need to extend the context as much as possible const context = { @@ -59,6 +65,9 @@ exports.sendEmail = async ctx => { user = db.get(userId) } const config = await determineScopedConfig(db, params) + if (!config) { + ctx.throw(400, "Unable to find SMTP configuration") + } const transport = createSMTPTransport(config) const message = { from: config.from, diff --git a/packages/worker/src/api/controllers/admin/templates.js b/packages/worker/src/api/controllers/admin/templates.js index 8d4065b77d..30c90d50bf 100644 --- a/packages/worker/src/api/controllers/admin/templates.js +++ b/packages/worker/src/api/controllers/admin/templates.js @@ -1,10 +1,13 @@ const { generateTemplateID, StaticDatabases } = require("@budibase/auth").db const { CouchDB } = require("../../../db") -const { TemplateMetadata, TemplateBindings } = require("../../../constants") +const { + TemplateMetadata, + TemplateBindings, + GLOBAL_OWNER, +} = require("../../../constants") const { getTemplates } = require("../../../constants/templates") const GLOBAL_DB = StaticDatabases.GLOBAL.name -const GLOBAL_OWNER = "global" exports.save = async ctx => { const db = new CouchDB(GLOBAL_DB) diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index 95dd474e9a..146373e671 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -11,7 +11,7 @@ const FIRST_USER_EMAIL = "test@test.com" const FIRST_USER_PASSWORD = "test" const GLOBAL_DB = StaticDatabases.GLOBAL.name -exports.userSave = async ctx => { +exports.save = async ctx => { const db = new CouchDB(GLOBAL_DB) const { email, password, _id } = ctx.request.body @@ -69,10 +69,10 @@ exports.firstUser = async ctx => { global: true, }, } - await exports.userSave(ctx) + await exports.save(ctx) } -exports.userDelete = async ctx => { +exports.destroy = async ctx => { const db = new CouchDB(GLOBAL_DB) const dbUser = await db.get(ctx.params.id) await db.remove(dbUser._id, dbUser._rev) @@ -82,7 +82,7 @@ exports.userDelete = async ctx => { } // called internally by app server user fetch -exports.userFetch = async ctx => { +exports.fetch = async ctx => { const db = new CouchDB(GLOBAL_DB) const response = await db.allDocs( getGlobalUserParams(null, { @@ -100,7 +100,7 @@ exports.userFetch = async ctx => { } // called internally by app server user find -exports.userFind = async ctx => { +exports.find = async ctx => { const db = new CouchDB(GLOBAL_DB) let user try { diff --git a/packages/worker/src/api/routes/admin/configs.js b/packages/worker/src/api/routes/admin/configs.js index 826f9535cc..4b9b8396cf 100644 --- a/packages/worker/src/api/routes/admin/configs.js +++ b/packages/worker/src/api/routes/admin/configs.js @@ -25,7 +25,7 @@ function smtpValidation() { function settingValidation() { // prettier-ignore return Joi.object({ - url: Joi.string().valid("", null), + platformUrl: Joi.string().valid("", null), logoUrl: Joi.string().valid("", null), company: Joi.string().required(), }).unknown(true) diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/admin/users.js index f06153385e..d4bc4b6b62 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/admin/users.js @@ -25,10 +25,10 @@ function buildUserSaveValidation() { } router - .post("/api/admin/users", buildUserSaveValidation(), controller.userSave) - .get("/api/admin/users", controller.userFetch) + .post("/api/admin/users", buildUserSaveValidation(), controller.save) + .get("/api/admin/users", controller.fetch) .post("/api/admin/users/first", controller.firstUser) - .delete("/api/admin/users/:id", controller.userDelete) - .get("/api/admin/users/:id", controller.userFind) + .delete("/api/admin/users/:id", controller.destroy) + .get("/api/admin/users/:id", controller.find) module.exports = router diff --git a/packages/worker/src/api/routes/tests/email.spec.js b/packages/worker/src/api/routes/tests/email.spec.js index b67bce2bed..797b0326ed 100644 --- a/packages/worker/src/api/routes/tests/email.spec.js +++ b/packages/worker/src/api/routes/tests/email.spec.js @@ -5,12 +5,19 @@ const { EmailTemplatePurpose } = require("../../../constants") const sendMailMock = jest.fn() jest.mock("nodemailer") const nodemailer = require("nodemailer") -nodemailer.createTransport.mockReturnValue({"sendMail": sendMailMock}); +nodemailer.createTransport.mockReturnValue({ + sendMail: sendMailMock, + verify: jest.fn() +}) describe("/api/admin/email", () => { let request = setup.getRequest() let config = setup.getConfig() + beforeAll(async () => { + await config.init() + }) + afterAll(setup.afterAll) it("should be able to send an email (with mocking)", async () => { @@ -26,5 +33,10 @@ describe("/api/admin/email", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) + expect(res.body.message).toBeDefined() + expect(sendMailMock).toHaveBeenCalled() + const emailCall = sendMailMock.mock.calls[0][0] + expect(emailCall.subject).toBe("Hello!") + expect(emailCall.html).not.toContain("Invalid Binding") }) }) \ No newline at end of file diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 6e499642d7..900e5dea13 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -39,6 +39,8 @@ class TestConfiguration { async init() { // create a test user await this._req({ + email: "test@test.com", + password: "test", _id: "us_uuid1", builder: { global: true, @@ -64,7 +66,7 @@ class TestConfiguration { await this._req({ type: Configs.SETTINGS, config: { - url: "http://localhost:10000", + platformUrl: "http://localhost:10000", logoUrl: "http://localhost:10000/logo", company: "TestCompany", } @@ -78,7 +80,7 @@ class TestConfiguration { port: 12345, host: "smtptesthost.com", from: "testfrom@test.com", - + subject: "Hello!", } }, null, controllers.config.save) } diff --git a/packages/worker/src/api/routes/tests/utilities/controllers.js b/packages/worker/src/api/routes/tests/utilities/controllers.js index e74e6dc355..b0d2441c0a 100644 --- a/packages/worker/src/api/routes/tests/utilities/controllers.js +++ b/packages/worker/src/api/routes/tests/utilities/controllers.js @@ -1,7 +1,7 @@ module.exports = { email: require("../../../controllers/admin/email"), groups: require("../../../controllers/admin/groups"), - config: require("../../../controllers/admin/groups"), - templates: require("../../../controllers/admin/groups"), - users: require("../../../controllers/admin/groups"), + config: require("../../../controllers/admin/configs"), + templates: require("../../../controllers/admin/templates"), + users: require("../../../controllers/admin/users"), } diff --git a/packages/worker/src/constants/index.js b/packages/worker/src/constants/index.js index 7406db0bdd..3bc8dbc039 100644 --- a/packages/worker/src/constants/index.js +++ b/packages/worker/src/constants/index.js @@ -30,7 +30,7 @@ const EmailTemplatePurpose = { } const TemplateBindings = { - URL: "url", + PLATFORM_URL: "platformUrl", COMPANY: "company", LOGO_URL: "logoUrl", STYLES: "styles", @@ -76,3 +76,4 @@ exports.TemplateTypes = TemplateTypes exports.EmailTemplatePurpose = EmailTemplatePurpose exports.TemplateMetadata = TemplateMetadata exports.TemplateBindings = TemplateBindings +exports.GLOBAL_OWNER = "global" diff --git a/packages/worker/src/constants/templates/base.html b/packages/worker/src/constants/templates/base.hbs similarity index 94% rename from packages/worker/src/constants/templates/base.html rename to packages/worker/src/constants/templates/base.hbs index f728404be8..1d8ff52700 100644 --- a/packages/worker/src/constants/templates/base.html +++ b/packages/worker/src/constants/templates/base.hbs @@ -47,7 +47,7 @@
-

Budibase Platform

+

Budibase Platform

@@ -64,7 +64,7 @@
-

© 2021 Restobar. All Rights Reserved

+

© 2021 {{ company }}. All Rights Reserved

@@ -79,4 +79,5 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.js index 1333baa39a..dd375d1e86 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.js @@ -3,6 +3,7 @@ const { EmailTemplatePurpose, TemplateTypes, TemplatePurpose, + GLOBAL_OWNER, } = require("../index") const { join } = require("path") const CouchDB = require("../../db") @@ -10,14 +11,14 @@ const { getTemplateParams, StaticDatabases } = require("@budibase/auth").db exports.EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( - join(__dirname, "passwordRecovery.html") + join(__dirname, "passwordRecovery.hbs") ), [EmailTemplatePurpose.INVITATION]: readStaticFile( - join(__dirname, "invitation.html") + join(__dirname, "invitation.hbs") ), - [EmailTemplatePurpose.BASE]: readStaticFile(join(__dirname, "base.html")), + [EmailTemplatePurpose.BASE]: readStaticFile(join(__dirname, "base.hbs")), [EmailTemplatePurpose.STYLES]: readStaticFile( - join(__dirname, "style.css") + join(__dirname, "style.hbs") ), } @@ -37,7 +38,11 @@ exports.addBaseTemplates = (templates, type = null) => { continue } if (exports.EmailTemplates[purpose]) { - templates.push(exports.EmailTemplates[purpose]) + templates.push({ + contents: exports.EmailTemplates[purpose], + purpose, + type, + }) } } return templates @@ -46,7 +51,7 @@ exports.addBaseTemplates = (templates, type = null) => { exports.getTemplates = async ({ ownerId, type, id } = {}) => { const db = new CouchDB(StaticDatabases.GLOBAL.name) const response = await db.allDocs( - getTemplateParams(ownerId, id, { + getTemplateParams(ownerId || GLOBAL_OWNER, id, { include_docs: true, }) ) diff --git a/packages/worker/src/constants/templates/invitation.html b/packages/worker/src/constants/templates/invitation.hbs similarity index 100% rename from packages/worker/src/constants/templates/invitation.html rename to packages/worker/src/constants/templates/invitation.hbs diff --git a/packages/worker/src/constants/templates/passwordRecovery.html b/packages/worker/src/constants/templates/passwordRecovery.hbs similarity index 100% rename from packages/worker/src/constants/templates/passwordRecovery.html rename to packages/worker/src/constants/templates/passwordRecovery.hbs diff --git a/packages/worker/src/constants/templates/style.css b/packages/worker/src/constants/templates/style.hbs similarity index 100% rename from packages/worker/src/constants/templates/style.css rename to packages/worker/src/constants/templates/style.hbs diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 22a9268366..53c492557b 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -15,17 +15,18 @@ exports.getSettingsTemplateContext = async () => { }) ) let settings = response.rows.map(row => row.doc)[0] || {} - if (!settings.url) { - settings.url = LOCAL_URL + if (!settings.platformUrl) { + settings.platformUrl = LOCAL_URL } // TODO: need to fully spec out the context + const URL = settings.platformUrl return { [TemplateBindings.LOGO_URL]: settings.logoUrl || LOGO_URL, - [TemplateBindings.URL]: settings.url, + [TemplateBindings.PLATFORM_URL]: URL, [TemplateBindings.REGISTRATION_URL]: checkSlashesInUrl( - `${settings.url}/registration` + `${URL}/registration` ), - [TemplateBindings.RESET_URL]: checkSlashesInUrl(`${settings.url}/reset`), + [TemplateBindings.RESET_URL]: checkSlashesInUrl(`${URL}/reset`), [TemplateBindings.COMPANY]: settings.company || BASE_COMPANY, } }