diff --git a/lerna.json b/lerna.json index 4b1c967e11..99915ab67b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.159-alpha.3", + "version": "1.0.163", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index fb6d9da990..84f1999ead 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "private": true, "devDependencies": { "@rollup/plugin-json": "^4.0.2", + "@types/mongodb": "3.6.3", + "@typescript-eslint/parser": "4.28.0", "babel-eslint": "^10.0.3", "eslint": "^7.28.0", "eslint-plugin-cypress": "^2.11.3", @@ -16,7 +18,6 @@ "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", "svelte": "^3.38.2", - "@typescript-eslint/parser": "4.28.0", "typescript": "4.5.5" }, "scripts": { diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7464b90725..68df3f3f4c 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.js index 96c7f99953..9b8019575c 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.js +++ b/packages/backend-core/src/middleware/passport/datasource/google.js @@ -1,7 +1,7 @@ const google = require("../google") const { Cookies, Configs } = require("../../../constants") const { clearCookie, getCookie } = require("../../../utils") -const { getScopedConfig, getPlatformUrl } = require("../../../db/utils") +const { getScopedConfig } = require("../../../db/utils") const { doWithDB } = require("../../../db") const environment = require("../../../environment") const { getGlobalDB } = require("../../../tenancy") @@ -21,20 +21,28 @@ async function fetchGoogleCreds() { ) } -async function platformUrl() { +async function getPlatformUrl() { + let platformUrl = environment.PLATFORM_URL || "http://localhost:10000" + const db = getGlobalDB() - const publicConfig = await getScopedConfig(db, { + const settings = await getScopedConfig(db, { type: Configs.SETTINGS, }) - return getPlatformUrl(publicConfig) + + // self hosted - check for platform url override + if (settings && settings.platformUrl) { + platformUrl = settings.platformUrl + } + + return platformUrl } async function preAuth(passport, ctx, next) { // get the relevant config const googleConfig = await fetchGoogleCreds() - const platUrl = await platformUrl() + const platformUrl = await getPlatformUrl() - let callbackUrl = `${platUrl}/api/global/auth/datasource/google/callback` + let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` const strategy = await google.strategyFactory(googleConfig, callbackUrl) if (!ctx.query.appId || !ctx.query.datasourceId) { @@ -51,9 +59,9 @@ async function preAuth(passport, ctx, next) { async function postAuth(passport, ctx, next) { // get the relevant config const config = await fetchGoogleCreds() - const platUrl = await platformUrl() + const platformUrl = await getPlatformUrl() - let callbackUrl = `${platUrl}/api/global/auth/datasource/google/callback` + let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` const strategy = await google.strategyFactory( config, callbackUrl, diff --git a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js index bfe9f97dc0..c5e9fe0034 100644 --- a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js @@ -71,7 +71,7 @@ describe("oidc", () => { describe("authenticate", () => { afterEach(() => { - jest.clearAllMocks(); + jest.clearAllMocks() }); // mock third party common authentication @@ -80,10 +80,10 @@ describe("oidc", () => { // mock the passport callback const mockDone = jest.fn() + const mockSaveUserFn = jest.fn() async function doAuthenticate() { const oidc = require("../oidc") - const mockSaveUserFn = jest.fn() const authenticate = await oidc.buildVerifyFn(mockSaveUserFn) await authenticate( @@ -105,11 +105,13 @@ describe("oidc", () => { expect(authenticateThirdParty).toHaveBeenCalledWith( user, false, - mockDone) + mockDone, + mockSaveUserFn, + ) } it("delegates authentication to third party common", async () => { - doTest() + await doTest() }) it("uses JWT email to get email", async () => { @@ -118,7 +120,7 @@ describe("oidc", () => { email : "mock@budibase.com" } - doTest() + await doTest() }) it("uses JWT username to get email", async () => { @@ -127,7 +129,7 @@ describe("oidc", () => { preferred_username : "mock@budibase.com" } - doTest() + await doTest() }) it("uses JWT invalid username to get email", async () => { diff --git a/packages/bbui/package.json b/packages/bbui/package.json index b00d3fc993..3147880fcb 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "^1.0.159-alpha.3", + "@budibase/string-templates": "^1.0.163", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 1d0d32b03c..73ba7bb642 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -58,6 +58,11 @@ if (timeOnly) { newValue = `2000-01-01T${newValue.split("T")[1]}` } + // date only, offset for timezone so always right date + else if (!enableTime) { + const offset = dates[0].getTimezoneOffset() * 60000 + newValue = new Date(dates[0].getTime() - offset).toISOString() + } dispatch("change", newValue) } diff --git a/packages/builder/package.json b/packages/builder/package.json index b7db9f6375..95611c6713 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "license": "GPL-3.0", "private": true, "scripts": { @@ -67,10 +67,10 @@ } }, "dependencies": { - "@budibase/bbui": "^1.0.159-alpha.3", - "@budibase/client": "^1.0.159-alpha.3", - "@budibase/frontend-core": "^1.0.159-alpha.3", - "@budibase/string-templates": "^1.0.159-alpha.3", + "@budibase/bbui": "^1.0.163", + "@budibase/client": "^1.0.163", + "@budibase/frontend-core": "^1.0.163", + "@budibase/string-templates": "^1.0.163", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/components/automation/Shared/WebhookDisplay.svelte b/packages/builder/src/components/automation/Shared/WebhookDisplay.svelte index dcd96ce2b9..9ba4140b51 100644 --- a/packages/builder/src/components/automation/Shared/WebhookDisplay.svelte +++ b/packages/builder/src/components/automation/Shared/WebhookDisplay.svelte @@ -12,4 +12,4 @@ } - + diff --git a/packages/builder/src/components/common/inputs/CopyInput.svelte b/packages/builder/src/components/common/inputs/CopyInput.svelte index 102fd5682a..589623f542 100644 --- a/packages/builder/src/components/common/inputs/CopyInput.svelte +++ b/packages/builder/src/components/common/inputs/CopyInput.svelte @@ -3,7 +3,6 @@ export let label = null export let value - export let copyValue export let dataCy = null const copyToClipboard = val => { @@ -19,7 +18,7 @@
-
copyToClipboard(value || copyValue)}> +
copyToClipboard(value)}>
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte index 5a60bfdff8..a9399fcca7 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte @@ -14,7 +14,10 @@ let options = roles .filter(role => role._id !== "PUBLIC") .map(role => ({ value: role._id, label: role.name })) - options.push({ value: NO_ACCESS, label: "No Access" }) + + if (!user?.builder?.global) { + options.push({ value: NO_ACCESS, label: "No Access" }) + } let selectedRole = user?.roles?.[app?._id] async function updateUserRoles() { diff --git a/packages/cli/package.json b/packages/cli/package.json index 31bdbc910c..340f51c434 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 80251bc351..64f8b2a32c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^1.0.159-alpha.3", - "@budibase/frontend-core": "^1.0.159-alpha.3", - "@budibase/string-templates": "^1.0.159-alpha.3", + "@budibase/bbui": "^1.0.163", + "@budibase/frontend-core": "^1.0.163", + "@budibase/string-templates": "^1.0.163", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index c6f0ff4bec..c10eedf983 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "^1.0.159-alpha.3", + "@budibase/bbui": "^1.0.163", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/server/package.json b/packages/server/package.json index 920bb66efb..ecc02aef4f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -69,10 +69,10 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@budibase/backend-core": "^1.0.159-alpha.3", - "@budibase/client": "^1.0.159-alpha.3", - "@budibase/pro": "1.0.159-alpha.3", - "@budibase/string-templates": "^1.0.159-alpha.3", + "@budibase/backend-core": "^1.0.163", + "@budibase/client": "^1.0.163", + "@budibase/pro": "1.0.163", + "@budibase/string-templates": "^1.0.163", "@bull-board/api": "^3.7.0", "@bull-board/koa": "^3.7.0", "@elastic/elasticsearch": "7.10.0", diff --git a/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap b/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap index 27c8615a46..4572a8a24f 100644 --- a/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +++ b/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap @@ -1,9 +1,66 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`viewBuilder Calculate and filter creates a view with the calculation statistics and filter schema 1`] = ` +Object { + "map": "function (doc) { + if ((doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && !( + doc[\\"myField\\"] === undefined || + doc[\\"myField\\"] === null || + doc[\\"myField\\"] === \\"\\" || + (Array.isArray(doc[\\"myField\\"]) && doc[\\"myField\\"].length === 0) + )) && (doc[\\"age\\"] > 17)) { + emit(doc[\\"_id\\"], doc[\\"myField\\"]); + } + }", + "meta": Object { + "calculation": "stats", + "field": "myField", + "filters": Array [ + Object { + "condition": "MT", + "key": "age", + "value": 17, + }, + ], + "groupBy": undefined, + "schema": Object { + "avg": Object { + "type": "number", + }, + "count": Object { + "type": "number", + }, + "field": Object { + "type": "string", + }, + "max": Object { + "type": "number", + }, + "min": Object { + "type": "number", + }, + "sum": Object { + "type": "number", + }, + "sumsqr": Object { + "type": "number", + }, + }, + "tableId": "14f1c4e94d6a47b682ce89d35d4c78b0", + }, + "reduce": "_stats", +} +`; + exports[`viewBuilder Calculate creates a view with the calculation statistics schema 1`] = ` Object { "map": "function (doc) { - if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) { + if ((doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && !( + doc[\\"myField\\"] === undefined || + doc[\\"myField\\"] === null || + doc[\\"myField\\"] === \\"\\" || + (Array.isArray(doc[\\"myField\\"]) && doc[\\"myField\\"].length === 0) + )) ) { emit(doc[\\"_id\\"], doc[\\"myField\\"]); } }", diff --git a/packages/server/src/api/controllers/view/tests/viewBuilder.spec.js b/packages/server/src/api/controllers/view/tests/viewBuilder.spec.js index d1674bca08..58fb68cfa7 100644 --- a/packages/server/src/api/controllers/view/tests/viewBuilder.spec.js +++ b/packages/server/src/api/controllers/view/tests/viewBuilder.spec.js @@ -44,4 +44,22 @@ describe("viewBuilder", () => { })).toMatchSnapshot() }) }) + + describe("Calculate and filter", () => { + it("creates a view with the calculation statistics and filter schema", () => { + expect(viewTemplate({ + "name": "Calculate View", + "field": "myField", + "calculation": "stats", + "tableId": "14f1c4e94d6a47b682ce89d35d4c78b0", + "filters": [ + { + "value": 17, + "condition": "MT", + "key": "age", + } + ] + })).toMatchSnapshot() + }) + }) }); \ No newline at end of file diff --git a/packages/server/src/api/controllers/view/utils.js b/packages/server/src/api/controllers/view/utils.js index 59d169ef7f..5bddbf345c 100644 --- a/packages/server/src/api/controllers/view/utils.js +++ b/packages/server/src/api/controllers/view/utils.js @@ -7,6 +7,7 @@ const { } = require("../../../db/utils") const env = require("../../../environment") const { getAppDB } = require("@budibase/backend-core/context") +const viewBuilder = require("./viewBuilder") exports.getView = async viewName => { const db = getAppDB() @@ -114,7 +115,8 @@ exports.deleteView = async viewName => { exports.migrateToInMemoryView = async (db, viewName) => { // delete the view initially const designDoc = await db.get("_design/database") - const view = designDoc.views[viewName] + // run the view back through the view builder to update it + const view = viewBuilder(designDoc.views[viewName].meta) delete designDoc.views[viewName] await db.put(designDoc) await exports.saveView(db, null, viewName, view) @@ -123,7 +125,7 @@ exports.migrateToInMemoryView = async (db, viewName) => { exports.migrateToDesignView = async (db, viewName) => { let view = await db.get(generateMemoryViewID(viewName)) const designDoc = await db.get("_design/database") - designDoc.views[viewName] = view.view + designDoc.views[viewName] = viewBuilder(view.view.meta) await db.put(designDoc) await db.remove(view._id, view._rev) } diff --git a/packages/server/src/api/controllers/view/viewBuilder.js b/packages/server/src/api/controllers/view/viewBuilder.js index 6e2e5c8527..6596e0d9e7 100644 --- a/packages/server/src/api/controllers/view/viewBuilder.js +++ b/packages/server/src/api/controllers/view/viewBuilder.js @@ -10,6 +10,12 @@ const TOKEN_MAP = { OR: "||", } +const CONDITIONS = { + EMPTY: "EMPTY", + NOT_EMPTY: "NOT_EMPTY", + CONTAINS: "CONTAINS", +} + const isEmptyExpression = key => { return `( doc["${key}"] === undefined || @@ -77,13 +83,13 @@ function parseFilterExpression(filters) { expression.push(TOKEN_MAP[filter.conjunction]) } - if (filter.condition === "CONTAINS") { + if (filter.condition === CONDITIONS.CONTAINS) { expression.push( `doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")` ) - } else if (filter.condition === "EMPTY") { + } else if (filter.condition === CONDITIONS.EMPTY) { expression.push(isEmptyExpression(filter.key)) - } else if (filter.condition === "NOT_EMPTY") { + } else if (filter.condition === CONDITIONS.NOT_EMPTY) { expression.push(`!${isEmptyExpression(filter.key)}`) } else { const value = @@ -125,22 +131,37 @@ function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) { if (filters && filters.length > 0 && filters[0].conjunction) { delete filters[0].conjunction } - const parsedFilters = parseFilterExpression(filters) - const filterExpression = parsedFilters ? `&& (${parsedFilters})` : "" - const emitExpression = parseEmitExpression(field, groupBy) - - const reduction = field && calculation ? { reduce: `_${calculation}` } : {} - - let schema = null + let schema = null, + statFilter = null if (calculation) { schema = { ...(groupBy ? GROUP_PROPERTY : FIELD_PROPERTY), ...SCHEMA_MAP[calculation], } + if ( + !filters.find( + filter => + filter.key === field && filter.condition === CONDITIONS.NOT_EMPTY + ) + ) { + statFilter = parseFilterExpression([ + { key: field, condition: CONDITIONS.NOT_EMPTY }, + ]) + } } + const parsedFilters = parseFilterExpression(filters) + const filterExpression = parsedFilters ? `&& (${parsedFilters})` : "" + + const emitExpression = parseEmitExpression(field, groupBy) + const tableExpression = `doc.tableId === "${tableId}"` + const coreExpression = statFilter + ? `(${tableExpression} && ${statFilter})` + : tableExpression + const reduction = field && calculation ? { reduce: `_${calculation}` } : {} + return { meta: { field, @@ -151,7 +172,7 @@ function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) { calculation, }, map: `function (doc) { - if (doc.tableId === "${tableId}" ${filterExpression}) { + if (${coreExpression} ${filterExpression}) { ${emitExpression} } }`, diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index c955b43a65..ae6754907b 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -4,10 +4,18 @@ import { QueryTypes, } from "../definitions/datasource" import { IntegrationBase } from "./base/IntegrationBase" +import { + MongoClient, + ObjectID, + FilterQuery, + UpdateQuery, + FindOneAndUpdateOption, + UpdateOneOptions, + UpdateManyOptions, + CommonOptions, +} from "mongodb" module MongoDBModule { - const { MongoClient } = require("mongodb") - interface MongoDBConfig { connectionString: string db: string @@ -76,20 +84,76 @@ module MongoDBModule { return this.client.connect() } + createObjectIds(json: any): object { + const self = this + function interpolateObjectIds(json: any) { + for (let field of Object.keys(json)) { + if (json[field] instanceof Object) { + json[field] = self.createObjectIds(json[field]) + } + if (field === "_id" && typeof json[field] === "string") { + const id = json["_id"].match( + /(?<=objectid\(['"]).*(?=['"]\))/gi + )?.[0] + if (id) { + json["_id"] = ObjectID.createFromHexString(id) + } + } + } + return json + } + + if (Array.isArray(json)) { + for (let i = 0; i < json.length; i++) { + json[i] = interpolateObjectIds(json[i]) + } + return json + } + return interpolateObjectIds(json) + } + + parseQueryParams(params: string, mode: string) { + let queryParams = params.split(/(?<=(},)).*{/g) + let group1 = queryParams[0] + let group2 = queryParams[2] + let group3 = queryParams[4] + if (group1) { + group1 = JSON.parse(group1.replace(/,+$/, "")) + } + if (group2) { + group2 = JSON.parse("{" + group2.replace(/,+$/, "")) + } + if (group3) { + group3 = JSON.parse("{" + group3.replace(/,+$/, "")) + } + if (mode === "update") { + return { + filter: group1, + update: group2, + options: group3 ?? {}, + } + } + return { + filter: group1, + options: group2 ?? {}, + } + } + async create(query: { json: object; extra: { [key: string]: string } }) { try { await this.connect() const db = this.client.db(this.config.db) const collection = db.collection(query.extra.collection) + let json = this.createObjectIds(query.json) // For mongodb we add an extra actionType to specify // which method we want to call on the collection switch (query.extra.actionTypes) { case "insertOne": { - return await collection.insertOne(query.json) + return await collection.insertOne(json) } case "insertMany": { - return await collection.insertOne(query.json).toArray() + return await collection.insertMany(json) } default: { throw new Error( @@ -110,22 +174,32 @@ module MongoDBModule { await this.connect() const db = this.client.db(this.config.db) const collection = db.collection(query.extra.collection) + let json = this.createObjectIds(query.json) switch (query.extra.actionTypes) { case "find": { - return await collection.find(query.json).toArray() + return await collection.find(json).toArray() } case "findOne": { - return await collection.findOne(query.json) + return await collection.findOne(json) } case "findOneAndUpdate": { - return await collection.findOneAndUpdate(query.json) + let findAndUpdateJson = json as { + filter: FilterQuery + update: UpdateQuery + options: FindOneAndUpdateOption + } + return await collection.findOneAndUpdate( + findAndUpdateJson.filter, + findAndUpdateJson.update, + findAndUpdateJson.options + ) } case "count": { - return await collection.countDocuments(query.json) + return await collection.countDocuments(json) } case "distinct": { - return await collection.distinct(query.json) + return await collection.distinct(json) } default: { throw new Error( @@ -146,13 +220,30 @@ module MongoDBModule { await this.connect() const db = this.client.db(this.config.db) const collection = db.collection(query.extra.collection) + let queryJson = query.json + if (typeof queryJson === "string") { + queryJson = this.parseQueryParams(queryJson, "update") + } + let json = this.createObjectIds(queryJson) as { + filter: FilterQuery + update: UpdateQuery + options: object + } switch (query.extra.actionTypes) { case "updateOne": { - return await collection.updateOne(query.json) + return await collection.updateOne( + json.filter, + json.update, + json.options as UpdateOneOptions + ) } case "updateMany": { - return await collection.updateMany(query.json).toArray() + return await collection.updateMany( + json.filter, + json.update, + json.options as UpdateManyOptions + ) } default: { throw new Error( @@ -173,13 +264,21 @@ module MongoDBModule { await this.connect() const db = this.client.db(this.config.db) const collection = db.collection(query.extra.collection) + let queryJson = query.json + if (typeof queryJson === "string") { + queryJson = this.parseQueryParams(queryJson, "delete") + } + let json = this.createObjectIds(queryJson) as { + filter: FilterQuery + options: CommonOptions + } switch (query.extra.actionTypes) { case "deleteOne": { - return await collection.deleteOne(query.json) + return await collection.deleteOne(json.filter, json.options) } case "deleteMany": { - return await collection.deleteMany(query.json).toArray() + return await collection.deleteMany(json.filter, json.options) } default: { throw new Error( diff --git a/packages/server/src/integrations/tests/mongo.spec.js b/packages/server/src/integrations/tests/mongo.spec.js index 430ccc1c3a..b0a49521ec 100644 --- a/packages/server/src/integrations/tests/mongo.spec.js +++ b/packages/server/src/integrations/tests/mongo.spec.js @@ -4,19 +4,19 @@ jest.mock("mongodb") class TestConfiguration { constructor(config = {}) { - this.integration = new MongoDBIntegration.integration(config) + this.integration = new MongoDBIntegration.integration(config) } } function disableConsole() { - jest.spyOn(console, 'error'); - console.error.mockImplementation(() => {}); + jest.spyOn(console, "error") + console.error.mockImplementation(() => {}) - return console.error.mockRestore; + return console.error.mockRestore } describe("MongoDB Integration", () => { - let config + let config let indexName = "Users" beforeEach(() => { @@ -25,12 +25,12 @@ describe("MongoDB Integration", () => { it("calls the create method with the correct params", async () => { const body = { - name: "Hello" + name: "Hello", } await config.integration.create({ index: indexName, json: body, - extra: { collection: 'testCollection', actionTypes: 'insertOne'} + extra: { collection: "testCollection", actionTypes: "insertOne" }, }) expect(config.integration.client.insertOne).toHaveBeenCalledWith(body) }) @@ -38,9 +38,9 @@ describe("MongoDB Integration", () => { it("calls the read method with the correct params", async () => { const query = { json: { - address: "test" + address: "test", }, - extra: { collection: 'testCollection', actionTypes: 'find'} + extra: { collection: "testCollection", actionTypes: "find" }, } const response = await config.integration.read(query) expect(config.integration.client.find).toHaveBeenCalledWith(query.json) @@ -50,30 +50,47 @@ describe("MongoDB Integration", () => { it("calls the delete method with the correct params", async () => { const query = { json: { - id: "test" + filter: { + id: "test", + }, + options: { + opt: "option" + } }, - extra: { collection: 'testCollection', actionTypes: 'deleteOne'} + extra: { collection: "testCollection", actionTypes: "deleteOne" }, } await config.integration.delete(query) - expect(config.integration.client.deleteOne).toHaveBeenCalledWith(query.json) + expect(config.integration.client.deleteOne).toHaveBeenCalledWith(query.json.filter, query.json.options) }) it("calls the update method with the correct params", async () => { const query = { json: { - id: "test" + filter: { + id: "test", + }, + update: { + name: "TestName", + }, + options: { + upsert: false, + }, }, - extra: { collection: 'testCollection', actionTypes: 'updateOne'} + extra: { collection: "testCollection", actionTypes: "updateOne" }, } await config.integration.update(query) - expect(config.integration.client.updateOne).toHaveBeenCalledWith(query.json) + expect(config.integration.client.updateOne).toHaveBeenCalledWith( + query.json.filter, + query.json.update, + query.json.options + ) }) it("throws an error when an invalid query.extra.actionType is passed for each method", async () => { const restore = disableConsole() const query = { - extra: { collection: 'testCollection', actionTypes: 'deleteOne'} + extra: { collection: "testCollection", actionTypes: "deleteOne" }, } let error = null @@ -85,4 +102,4 @@ describe("MongoDB Integration", () => { expect(error).toBeDefined() restore() }) -}) \ No newline at end of file +}) diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index fd9eea9339..ef93d2c163 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1014,10 +1014,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.0.154": - version "1.0.154" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.154.tgz#c310834892e7621778b07579464955487c5c9830" - integrity sha512-mcZxt8XhGgOB4XRHKkWTvBEI4HGp2bo8qyzOJRCvDqlg56S9zqGJDl75Z0N/Wc8N3I53QRcxISerj48odX172A== +"@budibase/backend-core@1.0.160": + version "1.0.160" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.160.tgz#000e3b5a3ed91e73a542b4caa202a6f147d91294" + integrity sha512-XfAFU6sRPrCSEKlm58WeuPw8lUoJK+KwO0tcbT+bB2Nb7XCHplskryEgk/PM9ujRU6SMPDx11zKeqRebHlycbA== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -1091,12 +1091,12 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/pro@1.0.154": - version "1.0.154" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.154.tgz#f4e31e30376b206159b711224038141d73a1118e" - integrity sha512-+O6bemrcgyWG4a+D5dIOoZ+LGjW4aN7tRdFeZqoaIPCc1pA6zNtLUkM1nb+Laafuwq2Aht37vEuaRy7jfzVprA== +"@budibase/pro@1.0.160": + version "1.0.160" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.160.tgz#921c4e3f65b866d84292644dfd8793c4d0b667c7" + integrity sha512-p+Jhnk1n98CWCJXydSQSO7a+HDpqGAHekGQbOR7aayuwuoYzyOXxTcHNLdBp+3lkXhLSZq9oIwfEGpgdrrhXPA== dependencies: - "@budibase/backend-core" "1.0.154" + "@budibase/backend-core" "1.0.160" node-fetch "^2.6.1" "@budibase/standard-components@^0.9.139": diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 7caa7a216e..b054ba9d65 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 3b1a63de84..987c54c5c0 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "1.0.159-alpha.3", + "version": "1.0.163", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -31,9 +31,9 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "^1.0.159-alpha.3", - "@budibase/pro": "1.0.159-alpha.3", - "@budibase/string-templates": "^1.0.159-alpha.3", + "@budibase/backend-core": "^1.0.163", + "@budibase/pro": "1.0.163", + "@budibase/string-templates": "^1.0.163", "@koa/router": "^8.0.0", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "^0.3.0", diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index a5e4c2d9e7..1b31c8ef50 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -293,10 +293,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.0.154": - version "1.0.154" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.154.tgz#c310834892e7621778b07579464955487c5c9830" - integrity sha512-mcZxt8XhGgOB4XRHKkWTvBEI4HGp2bo8qyzOJRCvDqlg56S9zqGJDl75Z0N/Wc8N3I53QRcxISerj48odX172A== +"@budibase/backend-core@1.0.160": + version "1.0.160" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.160.tgz#000e3b5a3ed91e73a542b4caa202a6f147d91294" + integrity sha512-XfAFU6sRPrCSEKlm58WeuPw8lUoJK+KwO0tcbT+bB2Nb7XCHplskryEgk/PM9ujRU6SMPDx11zKeqRebHlycbA== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -321,12 +321,12 @@ uuid "^8.3.2" zlib "^1.0.5" -"@budibase/pro@1.0.154": - version "1.0.154" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.154.tgz#f4e31e30376b206159b711224038141d73a1118e" - integrity sha512-+O6bemrcgyWG4a+D5dIOoZ+LGjW4aN7tRdFeZqoaIPCc1pA6zNtLUkM1nb+Laafuwq2Aht37vEuaRy7jfzVprA== +"@budibase/pro@1.0.160": + version "1.0.160" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.160.tgz#921c4e3f65b866d84292644dfd8793c4d0b667c7" + integrity sha512-p+Jhnk1n98CWCJXydSQSO7a+HDpqGAHekGQbOR7aayuwuoYzyOXxTcHNLdBp+3lkXhLSZq9oIwfEGpgdrrhXPA== dependencies: - "@budibase/backend-core" "1.0.154" + "@budibase/backend-core" "1.0.160" node-fetch "^2.6.1" "@cspotcode/source-map-consumer@0.8.0": diff --git a/yarn.lock b/yarn.lock index 129b650c73..276dd62d08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -972,6 +972,13 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@types/bson@*": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.2.0.tgz#a2f71e933ff54b2c3bf267b67fa221e295a33337" + integrity sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg== + dependencies: + bson "*" + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" @@ -982,6 +989,19 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/mongodb@3.6.3": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.3.tgz#5655af409d9e32d5d5ae9a653abf3e5f9c83eb7a" + integrity sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q== + dependencies: + "@types/bson" "*" + "@types/node" "*" + +"@types/node@*": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506" + integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ== + "@types/node@>= 8": version "17.0.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" @@ -1300,6 +1320,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1361,6 +1386,13 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" +bson@*: + version "4.6.3" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.3.tgz#d1a9a0b84b9e84b62390811fc5580f6a8b1d858c" + integrity sha512-rAqP5hcUVJhXP2MCSNVsf0oM2OGU1So6A9pVRDYayvJ5+hygXHQApf87wd5NlhPM1J9RJnbqxIG/f8QTzRoQ4A== + dependencies: + buffer "^5.6.0" + btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" @@ -1371,6 +1403,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" @@ -2969,6 +3009,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" @@ -4663,12 +4708,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.2.2: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^2.3.1: +picomatch@^2.2.2, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==