diff --git a/packages/worker/package.json b/packages/worker/package.json
index fd43af7b0f..c81e99acf1 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -20,6 +20,7 @@
"license": "AGPL-3.0-or-later",
"dependencies": {
"@budibase/auth": "0.0.1",
+ "@budibase/string-templates": "^0.8.16",
"@koa/router": "^8.0.0",
"aws-sdk": "^2.811.0",
"bcryptjs": "^2.4.3",
diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js
index 67f3405fa4..df19dc9a56 100644
--- a/packages/worker/src/api/controllers/admin/configs.js
+++ b/packages/worker/src/api/controllers/admin/configs.js
@@ -1,6 +1,10 @@
const CouchDB = require("../../../db")
-const authPkg = require("@budibase/auth")
-const { utils, StaticDatabases } = authPkg
+const {
+ generateConfigID,
+ StaticDatabases,
+ getConfigParams,
+ determineScopedConfig,
+} = require("@budibase/auth").db
const GLOBAL_DB = StaticDatabases.GLOBAL.name
@@ -11,7 +15,7 @@ exports.save = async function(ctx) {
// Config does not exist yet
if (!configDoc._id) {
- configDoc._id = utils.generateConfigID({
+ configDoc._id = generateConfigID({
type,
group,
user,
@@ -33,12 +37,11 @@ exports.save = async function(ctx) {
exports.fetch = async function(ctx) {
const db = new CouchDB(GLOBAL_DB)
const response = await db.allDocs(
- utils.getConfigParams(undefined, {
+ getConfigParams(undefined, {
include_docs: true,
})
)
- const groups = response.rows.map(row => row.doc)
- ctx.body = groups
+ ctx.body = response.rows.map(row => row.doc)
}
/**
@@ -60,7 +63,7 @@ exports.find = async function(ctx) {
try {
// Find the config with the most granular scope based on context
- const scopedConfig = await authPkg.db.determineScopedConfig(db, {
+ const scopedConfig = await determineScopedConfig(db, {
type: ctx.params.type,
user: userId,
group,
diff --git a/packages/worker/src/api/controllers/admin/templates.js b/packages/worker/src/api/controllers/admin/templates.js
index 0314ca6099..f01f8e2176 100644
--- a/packages/worker/src/api/controllers/admin/templates.js
+++ b/packages/worker/src/api/controllers/admin/templates.js
@@ -1,59 +1,17 @@
const {
generateTemplateID,
- getTemplateParams,
StaticDatabases,
} = require("@budibase/auth").db
const { CouchDB } = require("../../../db")
const {
- TemplatePurposePretty,
- TemplateTypes,
- EmailTemplatePurpose,
- TemplatePurpose,
+ TemplateMetadata,
+ TemplateBindings,
} = require("../../../constants")
-const { getTemplateByPurpose } = require("../../../constants/templates")
+const { getTemplates } = require("../../../constants/templates")
const GLOBAL_DB = StaticDatabases.GLOBAL.name
const GLOBAL_OWNER = "global"
-function addBaseTemplates(templates, type = null) {
- let purposeList
- switch (type) {
- case TemplateTypes.EMAIL:
- purposeList = Object.values(EmailTemplatePurpose)
- break
- default:
- purposeList = Object.values(TemplatePurpose)
- break
- }
- for (let purpose of purposeList) {
- // check if a template exists already for purpose
- if (templates.find(template => template.purpose === purpose)) {
- continue
- }
- templates.push(getTemplateByPurpose(purpose))
- }
- return templates
-}
-
-async function getTemplates({ ownerId, type, id } = {}) {
- const db = new CouchDB(GLOBAL_DB)
- const response = await db.allDocs(
- getTemplateParams(ownerId, id, {
- include_docs: true,
- })
- )
- let templates = response.rows.map(row => row.doc)
- // should only be one template with ID
- if (id) {
- return templates[0]
- }
- if (type) {
- templates = templates.filter(template => template.type === type)
- }
-
- return addBaseTemplates(templates, type)
-}
-
exports.save = async ctx => {
const db = new CouchDB(GLOBAL_DB)
const type = ctx.params.type
@@ -77,7 +35,8 @@ exports.save = async ctx => {
exports.definitions = async ctx => {
ctx.body = {
- purpose: TemplatePurposePretty,
+ purpose: TemplateMetadata,
+ bindings: Object.values(TemplateBindings),
}
}
diff --git a/packages/worker/src/constants/index.js b/packages/worker/src/constants/index.js
index 44ca57ea17..bb002bbd6f 100644
--- a/packages/worker/src/constants/index.js
+++ b/packages/worker/src/constants/index.js
@@ -1,3 +1,6 @@
+exports.LOGO_URL =
+ "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
+
exports.UserStatus = {
ACTIVE: "active",
INACTIVE: "inactive",
@@ -19,39 +22,48 @@ const TemplateTypes = {
}
const EmailTemplatePurpose = {
- HEADER: "header",
- FOOTER: "footer",
+ BASE: "base",
STYLES: "styles",
PASSWORD_RECOVERY: "password_recovery",
INVITATION: "invitation",
CUSTOM: "custom",
}
-const TemplatePurposePretty = {
+const TemplateBindings = {
+ URL: "url",
+ COMPANY: "company",
+ LOGO_URL: "logoUrl",
+ STYLES: "styles",
+ BODY: "body",
+ REGISTRATION_URL: "registrationUrl",
+ EMAIL: "email",
+ RESET_URL: "resetUrl",
+ USER: "user",
+}
+
+const TemplateMetadata = {
[TemplateTypes.EMAIL]: [
{
name: "Styling",
- value: EmailTemplatePurpose.STYLES,
+ purpose: EmailTemplatePurpose.STYLES,
+ bindings: ["url", "company", "companyUrl", "styles", "body"]
},
{
- name: "Header",
- value: EmailTemplatePurpose.HEADER,
- },
- {
- name: "Footer",
- value: EmailTemplatePurpose.FOOTER,
+ name: "Base Format",
+ purpose: EmailTemplatePurpose.BASE,
+ bindings: ["company", "registrationUrl"]
},
{
name: "Password Recovery",
- value: EmailTemplatePurpose.PASSWORD_RECOVERY,
+ purpose: EmailTemplatePurpose.PASSWORD_RECOVERY,
},
{
name: "New User Invitation",
- value: EmailTemplatePurpose.INVITATION,
+ purpose: EmailTemplatePurpose.INVITATION,
},
{
name: "Custom",
- value: EmailTemplatePurpose.CUSTOM,
+ purpose: EmailTemplatePurpose.CUSTOM,
},
],
}
@@ -62,4 +74,5 @@ exports.TemplatePurpose = {
}
exports.TemplateTypes = TemplateTypes
exports.EmailTemplatePurpose = EmailTemplatePurpose
-exports.TemplatePurposePretty = TemplatePurposePretty
+exports.TemplateMetadata = TemplateMetadata
+exports.TemplateBindings = TemplateBindings
diff --git a/packages/worker/src/constants/templates/base.html b/packages/worker/src/constants/templates/base.html
new file mode 100644
index 0000000000..f728404be8
--- /dev/null
+++ b/packages/worker/src/constants/templates/base.html
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ {{ body }}
+ |
+
+
+
+
+ |
+
+
+
+
\ No newline at end of file
diff --git a/packages/worker/src/constants/templates/footer.html b/packages/worker/src/constants/templates/footer.html
deleted file mode 100644
index 693fd3c0c0..0000000000
--- a/packages/worker/src/constants/templates/footer.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/worker/src/constants/templates/header.html b/packages/worker/src/constants/templates/header.html
deleted file mode 100644
index 7709bd30a8..0000000000
--- a/packages/worker/src/constants/templates/header.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
- {{ body }}
- |
-
-
-
- {{ footer }}
- |
-
-
-
-
\ 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 53db5f2b42..2878a3a4e9 100644
--- a/packages/worker/src/constants/templates/index.js
+++ b/packages/worker/src/constants/templates/index.js
@@ -1,6 +1,11 @@
const { readStaticFile } = require("../../utilities/fileSystem")
-const { EmailTemplatePurpose } = require("../index")
+const { EmailTemplatePurpose, TemplateTypes, TemplatePurpose } = require("../index")
const { join } = require("path")
+const CouchDB = require("../../db")
+const {
+ getTemplateParams,
+ StaticDatabases,
+} = require("@budibase/auth").db
const TEMPLATE_PATH = join(__dirname, "..", "constants", "templates")
@@ -11,19 +16,56 @@ exports.EmailTemplates = {
[EmailTemplatePurpose.INVITATION]: readStaticFile(
join(TEMPLATE_PATH, "invitation.html")
),
- [EmailTemplatePurpose.HEADER]: readStaticFile(
- join(TEMPLATE_PATH, "header.html")
- ),
- [EmailTemplatePurpose.FOOTER]: readStaticFile(
- join(TEMPLATE_PATH, "footer.html")
+ [EmailTemplatePurpose.BASE]: readStaticFile(
+ join(TEMPLATE_PATH, "base.html")
),
[EmailTemplatePurpose.STYLES]: readStaticFile(
join(TEMPLATE_PATH, "style.css")
),
}
-exports.getTemplateByPurpose = purpose => {
- if (exports.EmailTemplates[purpose]) {
- return exports.EmailTemplates[purpose]
+exports.addBaseTemplates = (templates, type = null) => {
+ let purposeList
+ switch (type) {
+ case TemplateTypes.EMAIL:
+ purposeList = Object.values(EmailTemplatePurpose)
+ break
+ default:
+ purposeList = Object.values(TemplatePurpose)
+ break
}
+ for (let purpose of purposeList) {
+ // check if a template exists already for purpose
+ if (templates.find(template => template.purpose === purpose)) {
+ continue
+ }
+ if (exports.EmailTemplates[purpose]) {
+ templates.push(exports.EmailTemplates[purpose])
+ }
+ }
+ return templates
}
+
+exports.getTemplates = async ({ ownerId, type, id } = {}) => {
+ const db = new CouchDB(StaticDatabases.GLOBAL.name)
+ const response = await db.allDocs(
+ getTemplateParams(ownerId, id, {
+ include_docs: true,
+ })
+ )
+ let templates = response.rows.map(row => row.doc)
+ // should only be one template with ID
+ if (id) {
+ return templates[0]
+ }
+ if (type) {
+ templates = templates.filter(template => template.type === type)
+ }
+ return exports.addBaseTemplates(templates, type)
+}
+
+exports.getTemplateByPurpose = async (type, purpose) => {
+ const templates = await exports.getTemplates({ type })
+ return templates.find(template => template.purpose === purpose)
+}
+
diff --git a/packages/worker/src/constants/templates/passwordRecovery.html b/packages/worker/src/constants/templates/passwordRecovery.html
index e6b179ec81..11f4eac1f4 100644
--- a/packages/worker/src/constants/templates/passwordRecovery.html
+++ b/packages/worker/src/constants/templates/passwordRecovery.html
@@ -6,7 +6,7 @@
Budibase Password reset
Please follow the below link to reset your password.
Reset password
- This password reset was required for {{ user }} if you did not
+
This password reset was required for {{ email }} if you did not
request this then please contact your administrator.
diff --git a/packages/worker/src/utilities/email.js b/packages/worker/src/utilities/email.js
index e69de29bb2..6cab9cc4db 100644
--- a/packages/worker/src/utilities/email.js
+++ b/packages/worker/src/utilities/email.js
@@ -0,0 +1,36 @@
+const { EmailTemplatePurpose, TemplateTypes } = require("../constants")
+const { getTemplateByPurpose } = require("../constants/templates")
+const { processString } = require("@budibase/string-templates")
+const { getSettingsTemplateContext } = require("./templates")
+
+const TYPE = TemplateTypes.EMAIL
+
+const FULL_EMAIL_PURPOSES = [EmailTemplatePurpose.INVITATION, EmailTemplatePurpose.PASSWORD_RECOVERY]
+
+exports.buildEmail = async (email, user, purpose) => {
+ // this isn't a full email
+ if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) {
+ throw `Unable to build an email of type ${purpose}`
+ }
+ let [base, styles, body] = await Promise.all([
+ getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE),
+ getTemplateByPurpose(TYPE, EmailTemplatePurpose.STYLES),
+ getTemplateByPurpose(TYPE, purpose),
+ ])
+
+ // TODO: need to extend the context as much as possible
+ const context = {
+ ...await getSettingsTemplateContext(),
+ email,
+ user
+ }
+
+ body = await processString(body, context)
+ styles = await processString(styles, context)
+ // this should now be the complete email HTML
+ return processString(base, {
+ ...context,
+ styles,
+ body,
+ })
+}
\ No newline at end of file
diff --git a/packages/worker/src/utilities/index.js b/packages/worker/src/utilities/index.js
new file mode 100644
index 0000000000..b402a82cf3
--- /dev/null
+++ b/packages/worker/src/utilities/index.js
@@ -0,0 +1,9 @@
+/**
+ * Makes sure that a URL has the correct number of slashes, while maintaining the
+ * http(s):// double slashes.
+ * @param {string} url The URL to test and remove any extra double slashes.
+ * @return {string} The updated url.
+ */
+exports.checkSlashesInUrl = url => {
+ return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2")
+}
diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js
new file mode 100644
index 0000000000..064776647d
--- /dev/null
+++ b/packages/worker/src/utilities/templates.js
@@ -0,0 +1,29 @@
+const CouchDB = require("../../../db")
+const { getConfigParams, StaticDatabases } = require("@budibase/auth").db
+const { Configs, TemplateBindings, LOGO_URL } = require("../constants")
+const { checkSlashesInUrl } = require("./index")
+const env = require("../environment")
+
+const LOCAL_URL = `http://localhost:${env.PORT}`
+const BASE_COMPANY = "Budibase"
+
+exports.getSettingsTemplateContext = async () => {
+ const db = new CouchDB(StaticDatabases.GLOBAL.name)
+ const response = await db.allDocs(
+ getConfigParams(Configs.SETTINGS, {
+ include_docs: true,
+ })
+ )
+ let settings = response.rows.map(row => row.doc)[0] || {}
+ if (!settings.url) {
+ settings.url = LOCAL_URL
+ }
+ // TODO: need to fully spec out the context
+ return {
+ [TemplateBindings.LOGO_URL]: settings.logoUrl || LOGO_URL,
+ [TemplateBindings.URL]: settings.url,
+ [TemplateBindings.REGISTRATION_URL]: checkSlashesInUrl(`${settings.url}/registration`),
+ [TemplateBindings.RESET_URL]: checkSlashesInUrl(`${settings.url}/reset`),
+ [TemplateBindings.COMPANY]: settings.company || BASE_COMPANY,
+ }
+}
\ No newline at end of file