diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 222f921018..1129a9eda2 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -15,8 +15,9 @@ import { import { breakRowIdField, generateRowIdField, + isRowId, + convertRowId, } from "../../../integrations/utils" -import { RelationshipTypes } from "../../../constants" interface ManyRelationship { tableId?: string @@ -36,7 +37,7 @@ interface RunConfig { module External { const { makeExternalQuery } = require("./utils") - const { DataSourceOperation, FieldTypes } = require("../../../constants") + const { DataSourceOperation, FieldTypes, RelationshipTypes } = require("../../../constants") const { breakExternalTableId, isSQL } = require("../../../integrations/utils") const { processObjectSync } = require("@budibase/string-templates") const { cloneDeep } = require("lodash/fp") @@ -83,6 +84,48 @@ module External { } } + /** + * This function checks the incoming parameters to make sure all the inputs are + * valid based on on the table schema. The main thing this is looking for is when a + * user has made use of the _id field of a row for a foreign key or a search parameter. + * In these cases the key will be sent up as [1], rather than 1. In these cases we will + * simplify it down to the requirements. This function is quite complex as we try to be + * relatively restrictive over what types of columns we will perform this action for. + */ + function cleanupConfig(config: RunConfig, table: Table): RunConfig { + const primaryOptions = [ + FieldTypes.STRING, + FieldTypes.LONGFORM, + FieldTypes.OPTIONS, + FieldTypes.NUMBER, + ] + // filter out fields which cannot be keys + const fieldNames = Object.entries(table.schema) + .filter(schema => primaryOptions.find(val => val === schema[1].type)) + .map(([fieldName]) => fieldName) + const iterateObject = (obj: { [key: string]: any }) => { + for (let [field, value] of Object.entries(obj)) { + if (fieldNames.find(name => name === field) && isRowId(value)) { + obj[field] = convertRowId(value) + } + } + } + // check the row and filters to make sure they aren't a key of some sort + if (config.filters) { + for (let filter of Object.values(config.filters)) { + if (typeof filter !== "object" || Object.keys(filter).length === 0) { + continue + } + iterateObject(filter) + } + } + if (config.row) { + iterateObject(config.row) + } + + return config + } + function generateIdForRow(row: Row | undefined, table: Table): string { const primary = table.primary if (!row || !primary) { @@ -509,7 +552,7 @@ module External { return fields } - async run({ id, row, filters, sort, paginate }: RunConfig) { + async run(config: RunConfig) { const { appId, operation, tableId } = this let { datasourceId, tableName } = breakExternalTableId(tableId) if (!this.datasource) { @@ -525,9 +568,11 @@ module External { if (!table) { throw `Unable to process query, table "${tableName}" not defined.` } - // clean up row on ingress using schema + // look for specific components of config which may not be considered acceptable + let { id, row, filters, sort, paginate } = cleanupConfig(config, table) filters = buildFilters(id, filters || {}, table) const relationships = this.buildRelationships(table) + // clean up row on ingress using schema const processed = this.inputProcessing(row, table) row = processed.row if ( diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index 6e3dc6f684..45addef839 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -5,6 +5,7 @@ const { DocumentTypes, SEPARATOR } = require("../db/utils") const { FieldTypes } = require("../constants") const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` +const ROW_ID_REGEX = /^\[.*]$/g export function isExternalTable(tableId: string) { return tableId.includes(DocumentTypes.DATASOURCE) @@ -32,6 +33,20 @@ export function generateRowIdField(keyProps: any[] = []) { return encodeURIComponent(JSON.stringify(keyProps).replace(/"/g, "'")) } +export function isRowId(field: any) { + return Array.isArray(field) || (typeof field === "string" && field.match(ROW_ID_REGEX) != null) +} + +export function convertRowId(field: any) { + if (Array.isArray(field)) { + return field[0] + } + if (typeof field === "string" && field.match(ROW_ID_REGEX) != null) { + return field.substring(1, field.length - 1) + } + return field +} + // should always return an array export function breakRowIdField(_id: string | { _id: string }): any[] { if (!_id) {