From 8ff9635cd14e328a0bf38317d9d64f49a4fb4c3e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 23 Nov 2020 15:46:26 +0000 Subject: [PATCH] Removing all reference to 'pages' in server source code, now to look at builder. --- .../server/src/api/controllers/application.js | 98 ++-- .../server/src/automations/automationUtils.js | 36 -- packages/server/src/automations/thread.js | 22 +- packages/server/src/constants/layouts.js | 420 +++++++++--------- packages/server/src/utilities/mustache.js | 73 +++ 5 files changed, 335 insertions(+), 314 deletions(-) create mode 100644 packages/server/src/utilities/mustache.js diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 095cb3b831..6f5bf7f1e1 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -19,28 +19,45 @@ const { generateLayoutID, generateScreenID, } = require("../../db/utils") -const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") +const { + BUILTIN_LEVEL_IDS, + AccessController, +} = require("../../utilities/security/accessLevels") const { downloadExtractComponentLibraries, } = require("../../utilities/createAppPackage") -const { MAIN, UNAUTHENTICATED, LayoutTypes } = require("../../constants/layouts") +const { BASE_LAYOUTS } = require("../../constants/layouts") const { HOME_SCREEN } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") +const { recurseMustache } = require("../../utilities/mustache") const APP_PREFIX = DocumentTypes.APP + SEPARATOR // utility function, need to do away with this -async function getMainAndUnauthPage(db) { - let pages = await db.allDocs( - getLayoutParams(null, { - include_docs: true, - }) - ) - pages = pages.rows.map(row => row.doc) +async function getLayouts(db) { + return ( + await db.allDocs( + getLayoutParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) +} - const mainPage = pages.find(page => page.name === LayoutTypes.MAIN) - const unauthPage = pages.find(page => page.name === LayoutTypes.UNAUTHENTICATED) - return { mainPage, unauthPage } +async function getScreens(db) { + return ( + await db.allDocs( + getScreenParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) +} + +function getUserAccessLevelId(ctx) { + return !ctx.user.accessLevel || !ctx.user.accessLevel._id + ? BUILTIN_LEVEL_IDS.PUBLIC + : ctx.user.accessLevel._id } async function createInstance(template) { @@ -85,25 +102,16 @@ exports.fetch = async function(ctx) { exports.fetchAppDefinition = async function(ctx) { const db = new CouchDB(ctx.params.appId) - // TODO: need to get rid of pages here, they shouldn't be needed anymore - const { mainPage, unauthPage } = await getMainAndUnauthPage(db) - const userAccessLevelId = - !ctx.user.accessLevel || !ctx.user.accessLevel._id - ? BUILTIN_LEVEL_IDS.PUBLIC - : ctx.user.accessLevel._id - const correctPage = - userAccessLevelId === BUILTIN_LEVEL_IDS.PUBLIC ? unauthPage : mainPage - const screens = ( - await db.allDocs( - getScreenParams(correctPage._id, { - include_docs: true, - }) - ) - ).rows.map(row => row.doc) - // TODO: need to handle access control here, limit screens to user access level + const layouts = await getLayouts(db) + const userAccessLevelId = getUserAccessLevelId(ctx) + const accessController = new AccessController(ctx.params.appId) + const screens = accessController.checkScreensAccess( + await getScreens(db), + userAccessLevelId + ) ctx.body = { - page: correctPage, - screens: screens, + layouts, + screens, libraries: ["@budibase/standard-components"], } } @@ -111,16 +119,14 @@ exports.fetchAppDefinition = async function(ctx) { exports.fetchAppPackage = async function(ctx) { const db = new CouchDB(ctx.params.appId) const application = await db.get(ctx.params.appId) + const layouts = await getLayouts(db) + const screens = await getScreens(db) - const { mainPage, unauthPage } = await getMainAndUnauthPage(db) ctx.body = { application, - pages: { - main: mainPage, - unauthenticated: unauthPage, - }, + screens, + layouts, } - await setBuilderToken(ctx, ctx.params.appId, application.version) } @@ -193,18 +199,18 @@ const createEmptyAppPackage = async (ctx, app) => { fs.mkdirpSync(newAppFolder) - const mainPage = cloneDeep(MAIN) - mainPage._id = generateLayoutID() - mainPage.title = app.name - - const unauthPage = cloneDeep(UNAUTHENTICATED) - unauthPage._id = generateLayoutID() - unauthPage.title = app.name - unauthPage.props._children[0].title = `Log in to ${app.name}` + const bulkDocs = [] + for (let layout of BASE_LAYOUTS) { + const cloned = cloneDeep(layout) + cloned._id = generateLayoutID() + cloned.title = app.name + bulkDocs.push(recurseMustache(cloned, app)) + } const homeScreen = cloneDeep(HOME_SCREEN) - homeScreen._id = generateScreenID(mainPage._id) - await db.bulkDocs([mainPage, unauthPage, homeScreen]) + homeScreen._id = generateScreenID() + bulkDocs.push(homeScreen) + await db.bulkDocs(bulkDocs) await compileStaticAssets(app._id) return newAppFolder diff --git a/packages/server/src/automations/automationUtils.js b/packages/server/src/automations/automationUtils.js index 3a66420dd0..ac0d9bf721 100644 --- a/packages/server/src/automations/automationUtils.js +++ b/packages/server/src/automations/automationUtils.js @@ -1,41 +1,5 @@ const CouchDB = require("../db") -/** - * When running mustache statements to execute on the context of the automation it possible user's may input mustache - * in a few different forms, some of which are invalid but are logically valid. An example of this would be the mustache - * statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array - * like operators. These are not supported by Mustache and therefore the statement will fail. This function will clean up - * the mustache statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded - * to include any other mustache statement cleanup that has been deemed necessary for the system. - * - * @param {string} string The string which *may* contain mustache statements, it is OK if it does not contain any. - * @returns {string} The string that was input with cleaned up mustache statements as required. - */ -module.exports.cleanMustache = string => { - let charToReplace = { - "[": ".", - "]": "", - } - let regex = new RegExp(/{{[^}}]*}}/g) - let matches = string.match(regex) - if (matches == null) { - return string - } - for (let match of matches) { - let baseIdx = string.indexOf(match) - for (let key of Object.keys(charToReplace)) { - let idxChar = match.indexOf(key) - if (idxChar !== -1) { - string = - string.slice(baseIdx, baseIdx + idxChar) + - charToReplace[key] + - string.slice(baseIdx + idxChar + 1) - } - } - } - return string -} - /** * When values are input to the system generally they will be of type string as this is required for mustache. This can * generate some odd scenarios as the Schema of the automation requires a number but the builder might supply a string diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 26ca8f04a3..cc01ec2d4b 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -1,30 +1,10 @@ -const handlebars = require("handlebars") const actions = require("./actions") const logic = require("./logic") const automationUtils = require("./automationUtils") - -handlebars.registerHelper("object", value => { - return new handlebars.SafeString(JSON.stringify(value)) -}) +const { recurseMustache } = require("../utilities/mustache") const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId -function recurseMustache(inputs, context) { - for (let key of Object.keys(inputs)) { - let val = inputs[key] - if (typeof val === "string") { - val = automationUtils.cleanMustache(inputs[key]) - const template = handlebars.compile(val) - inputs[key] = template(context) - } - // this covers objects and arrays - else if (typeof val === "object") { - inputs[key] = recurseMustache(inputs[key], context) - } - } - return inputs -} - /** * The automation orchestrator is a class responsible for executing automations. * It handles the context of the automation and makes sure each step gets the correct diff --git a/packages/server/src/constants/layouts.js b/packages/server/src/constants/layouts.js index 065bcdf261..85ab7ee69d 100644 --- a/packages/server/src/constants/layouts.js +++ b/packages/server/src/constants/layouts.js @@ -1,221 +1,219 @@ -const LayoutTypes = { - MAIN: "main", - UNAUTHENTICATED: "unauthenticated", -} - -const MAIN = { - componentLibraries: ["@budibase/standard-components"], - title: "{{ name }}", - favicon: "./_shared/favicon.png", - stylesheets: [], - name: LayoutTypes.MAIN, - props: { - _id: "private-master-root", - _component: "@budibase/standard-components/container", - _children: [ - { - _id: "c74f07266980c4b6eafc33e2a6caa783d", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - display: "flex", - "flex-direction": "row", - "justify-content": "flex-start", - "align-items": "flex-start", - background: "#fff", - width: "100%", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf", - _instanceName: "Header", - _children: [ - { - _id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1", - _component: "@budibase/standard-components/Navigation", - _styles: { - normal: { - "max-width": "1400px", - "margin-left": "auto", - "margin-right": "auto", - padding: "20px", - color: "#757575", - "font-weight": "400", - "font-size": "16px", - flex: "1 1 auto", - }, - hover: {}, - active: {}, - selected: {}, +const BASE_LAYOUTS = [ + { + componentLibraries: ["@budibase/standard-components"], + title: "{{ name }}", + favicon: "./_shared/favicon.png", + stylesheets: [], + name: "Main", + props: { + _id: "private-master-root", + _component: "@budibase/standard-components/container", + _children: [ + { + _id: "c74f07266980c4b6eafc33e2a6caa783d", + _component: "@budibase/standard-components/container", + _styles: { + normal: { + display: "flex", + "flex-direction": "row", + "justify-content": "flex-start", + "align-items": "flex-start", + background: "#fff", + width: "100%", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", }, - _code: "", - logoUrl: - "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", - title: "", - backgroundColor: "", - color: "", - borderWidth: "", - borderColor: "", - borderStyle: "", - _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", - _instanceName: "Navigation", - _children: [ - { - _id: "48b35328-4c91-4343-a6a3-1a1fd77b3386", - _component: "@budibase/standard-components/link", - _styles: { - normal: { - "font-family": "Inter", - "font-weight": "500", - color: "#000000", - "text-decoration-line": "none", - "font-size": "16px", - }, - hover: { - color: "#4285f4", - }, - active: {}, - selected: {}, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + type: "div", + _appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf", + _instanceName: "Header", + _children: [ + { + _id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1", + _component: "@budibase/standard-components/Navigation", + _styles: { + normal: { + "max-width": "1400px", + "margin-left": "auto", + "margin-right": "auto", + padding: "20px", + color: "#757575", + "font-weight": "400", + "font-size": "16px", + flex: "1 1 auto", }, - _code: "", - url: "/", - openInNewTab: false, - text: "Home", - color: "", - hoverColor: "", - underline: false, - fontSize: "", - fontFamily: "initial", - _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", - _instanceName: "Home Link", - _children: [], + hover: {}, + active: {}, + selected: {}, }, - ], - }, - ], - }, - { - _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", - _component: "##builtin/screenslot", - _styles: { - normal: { - flex: "1 1 auto", - display: "flex", - "flex-direction": "column", - "justify-content": "flex-start", - "align-items": "stretch", - "max-width": "100%", - "margin-left": "20px", - "margin-right": "20px", - width: "1400px", - padding: "20px", - }, - hover: {}, - active: {}, - selected: {}, + _code: "", + logoUrl: + "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + title: "", + backgroundColor: "", + color: "", + borderWidth: "", + borderColor: "", + borderStyle: "", + _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", + _instanceName: "Navigation", + _children: [ + { + _id: "48b35328-4c91-4343-a6a3-1a1fd77b3386", + _component: "@budibase/standard-components/link", + _styles: { + normal: { + "font-family": "Inter", + "font-weight": "500", + color: "#000000", + "text-decoration-line": "none", + "font-size": "16px", + }, + hover: { + color: "#4285f4", + }, + active: {}, + selected: {}, + }, + _code: "", + url: "/", + openInNewTab: false, + text: "Home", + color: "", + hoverColor: "", + underline: false, + fontSize: "", + fontFamily: "initial", + _appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394", + _instanceName: "Home Link", + _children: [], + }, + ], + }, + ], }, - _code: "", - _children: [], - }, - ], - type: "div", - _styles: { - active: {}, - hover: {}, - normal: { - display: "flex", - "flex-direction": "column", - "align-items": "center", - "justify-content": "flex-start", - "margin-right": "auto", - "margin-left": "auto", - "min-height": "100%", - "background-image": - "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", - }, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - }, -} - -const UNAUTHENTICATED = { - componentLibraries: ["@budibase/standard-components"], - title: "{{ name }}", - favicon: "./_shared/favicon.png", - stylesheets: [], - name: LayoutTypes.UNAUTHENTICATED, - props: { - _id: "public-master-root", - _component: "@budibase/standard-components/container", - _children: [ - { - _id: "686c252d-dbf2-4e28-9078-414ba4719759", - _component: "@budibase/standard-components/login", - _styles: { - normal: { - padding: "64px", - background: "rgba(255, 255, 255, 0.4)", - "border-radius": "0.5rem", - "margin-top": "0px", - margin: "0px", - "line-height": "1", - "box-shadow": - "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", - "font-size": "16px", - "font-family": "Inter", - flex: "0 1 auto", - transform: "0", + { + _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", + _component: "##builtin/screenslot", + _styles: { + normal: { + flex: "1 1 auto", + display: "flex", + "flex-direction": "column", + "justify-content": "flex-start", + "align-items": "stretch", + "max-width": "100%", + "margin-left": "20px", + "margin-right": "20px", + width: "1400px", + padding: "20px", + }, + hover: {}, + active: {}, + selected: {}, }, - hover: {}, - active: {}, - selected: {}, + _code: "", + _children: [], }, - _code: "", - loginRedirect: "", - usernameLabel: "Username", - passwordLabel: "Password", - loginButtonLabel: "Login", - buttonClass: "", - _instanceName: "Login", - inputClass: "", - _children: [], - title: "Log in to {{ name }}", - buttonText: "Log In", - logo: - "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + ], + type: "div", + _styles: { + active: {}, + hover: {}, + normal: { + display: "flex", + "flex-direction": "column", + "align-items": "center", + "justify-content": "flex-start", + "margin-right": "auto", + "margin-left": "auto", + "min-height": "100%", + "background-image": + "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", + }, + selected: {}, }, - ], - type: "div", - _styles: { - active: {}, - hover: {}, - normal: { - display: "flex", - "flex-direction": "column", - "align-items": "center", - "justify-content": "center", - "margin-right": "auto", - "margin-left": "auto", - "min-height": "100%", - "background-image": - "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", - }, - selected: {}, + _code: "", + className: "", + onLoad: [], }, - _code: "", - className: "", - onLoad: [], }, -} + { + componentLibraries: ["@budibase/standard-components"], + title: "{{ name }}", + favicon: "./_shared/favicon.png", + stylesheets: [], + name: "Unauthenticated", + props: { + _id: "public-master-root", + _component: "@budibase/standard-components/container", + _children: [ + { + _id: "686c252d-dbf2-4e28-9078-414ba4719759", + _component: "@budibase/standard-components/login", + _styles: { + normal: { + padding: "64px", + background: "rgba(255, 255, 255, 0.4)", + "border-radius": "0.5rem", + "margin-top": "0px", + margin: "0px", + "line-height": "1", + "box-shadow": + "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", + "font-size": "16px", + "font-family": "Inter", + flex: "0 1 auto", + transform: "0", + }, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + loginRedirect: "", + usernameLabel: "Username", + passwordLabel: "Password", + loginButtonLabel: "Login", + buttonClass: "", + _instanceName: "Login", + inputClass: "", + _children: [], + title: "Log in to {{ name }}", + buttonText: "Log In", + logo: + "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg", + }, + ], + type: "div", + _styles: { + active: {}, + hover: {}, + normal: { + display: "flex", + "flex-direction": "column", + "align-items": "center", + "justify-content": "center", + "margin-right": "auto", + "margin-left": "auto", + "min-height": "100%", + "background-image": + "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);", + }, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + }, + }, +] -module.exports = { MAIN, UNAUTHENTICATED, LayoutTypes } +module.exports = { + BASE_LAYOUTS, +} diff --git a/packages/server/src/utilities/mustache.js b/packages/server/src/utilities/mustache.js new file mode 100644 index 0000000000..0428bdc03d --- /dev/null +++ b/packages/server/src/utilities/mustache.js @@ -0,0 +1,73 @@ +const handlebars = require("handlebars") + +handlebars.registerHelper("object", value => { + return new handlebars.SafeString(JSON.stringify(value)) +}) + +/** + * When running mustache statements to execute on the context of the automation it possible user's may input mustache + * in a few different forms, some of which are invalid but are logically valid. An example of this would be the mustache + * statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array + * like operators. These are not supported by Mustache and therefore the statement will fail. This function will clean up + * the mustache statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded + * to include any other mustache statement cleanup that has been deemed necessary for the system. + * + * @param {string} string The string which *may* contain mustache statements, it is OK if it does not contain any. + * @returns {string} The string that was input with cleaned up mustache statements as required. + */ +function cleanMustache(string) { + let charToReplace = { + "[": ".", + "]": "", + } + let regex = new RegExp(/{{[^}}]*}}/g) + let matches = string.match(regex) + if (matches == null) { + return string + } + for (let match of matches) { + let baseIdx = string.indexOf(match) + for (let key of Object.keys(charToReplace)) { + let idxChar = match.indexOf(key) + if (idxChar !== -1) { + string = + string.slice(baseIdx, baseIdx + idxChar) + + charToReplace[key] + + string.slice(baseIdx + idxChar + 1) + } + } + } + return string +} + +/** + * Given an input object this will recurse through all props to try and update + * any handlebars/mustache statements within. + * @param {object|array} inputs The input structure which is to be recursed, it is important to note that + * if the structure contains any cycles then this will fail. + * @param {object} context The context that handlebars should fill data from. + * @returns {object|array} The structure input, as fully updated as possible. + */ +function recurseMustache(inputs, context) { + // JSON stringify will fail if there are any cycles, stops infinite recursion + try { + JSON.stringify(inputs) + } catch (err) { + throw "Unable to process inputs to JSON, cannot recurse" + } + for (let key of Object.keys(inputs)) { + let val = inputs[key] + if (typeof val === "string") { + val = cleanMustache(inputs[key]) + const template = handlebars.compile(val) + inputs[key] = template(context) + } + // this covers objects and arrays + else if (typeof val === "object") { + inputs[key] = recurseMustache(inputs[key], context) + } + } + return inputs +} + +exports.recurseMustache = recurseMustache