From 36432a490f08c53f6b5ccfca607c41247dcd53be Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 9 Dec 2020 10:52:18 +0000 Subject: [PATCH 1/7] Updating row controller to make sure that all user requests (bar deletion) are passed through correctly to the user controller so that any logic such as removing user password can be correctly held in the user controller logic. --- packages/server/src/api/controllers/row.js | 102 +++++------------- packages/server/src/api/controllers/user.js | 11 +- .../server/src/api/routes/tests/row.spec.js | 2 - packages/server/src/utilities/index.js | 94 ++++++++++++++++ 4 files changed, 128 insertions(+), 81 deletions(-) diff --git a/packages/server/src/api/controllers/row.js b/packages/server/src/api/controllers/row.js index 61dd666f3e..41d2ae15b1 100644 --- a/packages/server/src/api/controllers/row.js +++ b/packages/server/src/api/controllers/row.js @@ -9,7 +9,7 @@ const { ViewNames, } = require("../../db/utils") const usersController = require("./user") -const { cloneDeep } = require("lodash") +const { coerceRowValues } = require("../../utilities") const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` @@ -29,11 +29,24 @@ validateJs.extend(validateJs.validators.datetime, { }, }) -// lots of row functionality too specific to pass to user controller, simply handle the -// password deletion here -function removePassword(tableId, row) { +async function findRow(db, appId, tableId, rowId) { + let row if (tableId === ViewNames.USERS) { - delete row.password + let ctx = { + params: { + userId: rowId, + }, + user: { + appId, + }, + } + await usersController.find(ctx) + row = ctx.body + } else { + row = await db.get(rowId) + } + if (row.tableId !== tableId) { + throw "Supplied tableId does not match the rows tableId" } return row } @@ -224,13 +237,12 @@ exports.fetchTableRows = async function(ctx) { exports.find = async function(ctx) { const appId = ctx.user.appId const db = new CouchDB(appId) - let row = await db.get(ctx.params.rowId) - if (row.tableId !== ctx.params.tableId) { - ctx.throw(400, "Supplied tableId does not match the rows tableId") - return + try { + const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId) + ctx.body = await linkRows.attachLinkInfo(appId, row) + } catch (err) { + ctx.throw(400, err) } - row = removePassword(ctx.params.tableId, row) - ctx.body = await linkRows.attachLinkInfo(appId, row) } exports.destroy = async function(ctx) { @@ -296,8 +308,10 @@ exports.fetchEnrichedRow = async function(ctx) { return } // need table to work out where links go in row - let [table, row] = await Promise.all([db.get(tableId), db.get(rowId)]) - row = removePassword(tableId, row) + let [table, row] = await Promise.all([ + db.get(tableId), + findRow(db, appId, tableId, rowId), + ]) // get the link docs const linkVals = await linkRows.getLinkDocuments({ appId, @@ -327,68 +341,6 @@ exports.fetchEnrichedRow = async function(ctx) { ctx.status = 200 } -function coerceRowValues(record, table) { - const row = cloneDeep(record) - for (let [key, value] of Object.entries(row)) { - const field = table.schema[key] - if (!field) continue - - // eslint-disable-next-line no-prototype-builtins - if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { - row[key] = TYPE_TRANSFORM_MAP[field.type][value] - } else if (TYPE_TRANSFORM_MAP[field.type].parse) { - row[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) - } - } - return row -} - -const TYPE_TRANSFORM_MAP = { - link: { - "": [], - [null]: [], - [undefined]: undefined, - }, - options: { - "": "", - [null]: "", - [undefined]: undefined, - }, - string: { - "": "", - [null]: "", - [undefined]: undefined, - }, - longform: { - "": "", - [null]: "", - [undefined]: undefined, - }, - number: { - "": null, - [null]: null, - [undefined]: undefined, - parse: n => parseFloat(n), - }, - datetime: { - "": null, - [undefined]: undefined, - [null]: null, - }, - attachment: { - "": [], - [null]: [], - [undefined]: undefined, - }, - boolean: { - "": null, - [null]: null, - [undefined]: undefined, - true: true, - false: false, - }, -} - async function bulkDelete(ctx) { const appId = ctx.user.appId const { rows } = ctx.request.body diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 7ca2e05015..9ef7c2281f 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -89,9 +89,12 @@ exports.destroy = async function(ctx) { exports.find = async function(ctx) { const database = new CouchDB(ctx.user.appId) - const user = await database.get(generateUserID(ctx.params.email)) - ctx.body = { - email: user.email, - _rev: user._rev, + let lookup = ctx.params.email + ? generateUserID(ctx.params.email) + : ctx.params.userId + const user = await database.get(lookup) + if (user) { + delete user.password } + ctx.body = user } diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 5972e90ca9..0698250050 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -51,8 +51,6 @@ describe("/rows", () => { describe("save, load, update, delete", () => { - - it("returns a success message when the row is created", async () => { const res = await createRow() expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`) diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index cde10b6b62..8a43e9d27a 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,8 +1,59 @@ const env = require("../environment") const { DocumentTypes, SEPARATOR } = require("../db/utils") +const fs = require("fs") +const { cloneDeep } = require("lodash/fp") const APP_PREFIX = DocumentTypes.APP + SEPARATOR +/** + * A map of how we convert various properties in rows to each other based on the row type. + */ +const TYPE_TRANSFORM_MAP = { + link: { + "": [], + [null]: [], + [undefined]: undefined, + }, + options: { + "": "", + [null]: "", + [undefined]: undefined, + }, + string: { + "": "", + [null]: "", + [undefined]: undefined, + }, + longform: { + "": "", + [null]: "", + [undefined]: undefined, + }, + number: { + "": null, + [null]: null, + [undefined]: undefined, + parse: n => parseFloat(n), + }, + datetime: { + "": null, + [undefined]: undefined, + [null]: null, + }, + attachment: { + "": [], + [null]: [], + [undefined]: undefined, + }, + boolean: { + "": null, + [null]: null, + [undefined]: undefined, + true: true, + false: false, + }, +} + function confirmAppId(possibleAppId) { return possibleAppId && possibleAppId.startsWith(APP_PREFIX) ? possibleAppId @@ -74,3 +125,46 @@ exports.setCookie = (ctx, name, value) => { exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } + +/** + * Recursively walk a directory tree and execute a callback on all files. + * @param {String} dirPath - Directory to traverse + * @param {Function} callback - callback to execute on files + */ +exports.walkDir = (dirPath, callback) => { + for (let filename of fs.readdirSync(dirPath)) { + const filePath = `${dirPath}/${filename}` + const stat = fs.lstatSync(filePath) + + if (stat.isFile()) { + callback(filePath) + } else { + exports.walkDir(filePath, callback) + } + } +} + +/** + * This will coerce the values in a row to the correct types based on the type transform map and the + * table schema. + * @param {object} row The row which is to be coerced to correct values based on schema, this input + * row will not be updated. + * @param {object} table The table that has been retrieved from DB, this must contain the expected + * schema for the rows. + * @returns {object} The updated row will be returned with all values coerced. + */ +exports.coerceRowValues = (row, table) => { + const clonedRow = cloneDeep(row) + for (let [key, value] of Object.entries(clonedRow)) { + const field = table.schema[key] + if (!field) continue + + // eslint-disable-next-line no-prototype-builtins + if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { + clonedRow[key] = TYPE_TRANSFORM_MAP[field.type][value] + } else if (TYPE_TRANSFORM_MAP[field.type].parse) { + clonedRow[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) + } + } + return clonedRow +} From 05b9c3fa68bdf39794b3931d2590f84c4cc9a54a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 9 Dec 2020 11:31:50 +0000 Subject: [PATCH 2/7] Fix selected row state not resetting properly --- packages/builder/src/components/backend/DataTable/Table.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte index 3787c05a1b..e43a4f7408 100644 --- a/packages/builder/src/components/backend/DataTable/Table.svelte +++ b/packages/builder/src/components/backend/DataTable/Table.svelte @@ -56,6 +56,9 @@ } $: { + // Reset selection every time data changes + selectedRows = [] + let result = [] if (allowEditing) { result = [ From 31b3fac6590790fd07274db3b79d5679587b4556 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 9 Dec 2020 11:37:09 +0000 Subject: [PATCH 3/7] Fix crash when having a screen selected that was deleted --- packages/builder/src/builderStore/store/frontend.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 7be4f68f25..ea00a461fb 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -86,13 +86,16 @@ export const getFrontendStore = () => { select: async screenId => { let promise store.update(state => { - const screen = get(allScreens).find(screen => screen._id === screenId) + const screens = get(allScreens) + let selectedScreen = screens.find(screen => screen._id === screenId) + if (!selectedScreen) { + selectedScreen = screens[0] + } state.currentFrontEndType = FrontendTypes.SCREEN - state.currentAssetId = screenId + state.currentAssetId = selectedScreen._id state.currentView = "detail" - - promise = store.actions.screens.regenerateCss(screen) - state.selectedComponentId = screen.props._id + promise = store.actions.screens.regenerateCss(selectedScreen) + state.selectedComponentId = selectedScreen.props._id return state }) await promise From 22932d7b52b4cc0025e980407eba1536f9df6f65 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 9 Dec 2020 12:16:00 +0000 Subject: [PATCH 4/7] Fix routing setting and add initial role ID setting to screens for testing --- .../userInterface/ComponentPropertiesPanel.svelte | 3 ++- .../components/userInterface/SettingsView.svelte | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index b4ac25bdd5..4699a8e415 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -6,6 +6,7 @@ import CategoryTab from "./CategoryTab.svelte" import DesignView from "./DesignView.svelte" import SettingsView from "./SettingsView.svelte" + import { setWith } from "lodash" let flattenedPanel = flattenComponents(panelStructure.categories) let categories = [ @@ -69,7 +70,7 @@ ) { selectedAsset.props._instanceName = value } else { - selectedAsset[name] = value + setWith(selectedAsset, name.split("."), value, Object) } return state }) diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte index dd4644882c..f90e071b08 100644 --- a/packages/builder/src/components/userInterface/SettingsView.svelte +++ b/packages/builder/src/components/userInterface/SettingsView.svelte @@ -1,4 +1,5 @@