diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 0380f92253..96db14b77a 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -7,10 +7,12 @@ on: branches: - master - develop + - next pull_request: branches: - master - develop + - next jobs: build: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 792191af7a..14c0cc06a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,8 +52,8 @@ jobs: mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - windows_certs: ${{ secrets.windows_certs }} - windows_certs_password: ${{ secrets.windows_certs_password }} + # windows_certs: ${{ secrets.windows_certs }} + # windows_certs_password: ${{ secrets.windows_certs_password }} # release the app after building release: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/README.md b/README.md index 75b48a6107..04bb71fb50 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Code of conduct - +

diff --git a/lerna.json b/lerna.json index 3425a979b8..f63871e1c3 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.8.5", + "version": "0.8.9", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/builder/package.json b/packages/builder/package.json index ac957fa141..32389a985b 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.8.5", + "version": "0.8.9", "license": "AGPL-3.0", "private": true, "scripts": { @@ -64,9 +64,9 @@ }, "dependencies": { "@budibase/bbui": "^1.58.13", - "@budibase/client": "^0.8.5", + "@budibase/client": "^0.8.9", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.8.5", + "@budibase/string-templates": "^0.8.9", "@budibase/svelte-ag-grid": "^1.0.4", "@sentry/browser": "5.19.1", "@svelteschool/svelte-forms": "0.7.0", diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 46304e1486..0202c5e8ab 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -7,9 +7,10 @@ const apiCall = method => async ( headers = { "Content-Type": "application/json" } ) => { headers["x-budibase-app-id"] = svelteGet(store).appId + const json = headers["Content-Type"] === "application/json" return await fetch(url, { method: method, - body: body && JSON.stringify(body), + body: json ? JSON.stringify(body) : body, headers, }) } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index ad2371f3ea..d269124219 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -53,7 +53,11 @@ $: uneditable = $backendUiStore.selectedTable?._id === TableNames.USERS && UNEDITABLE_USER_FIELDS.includes(field.name) - $: invalid = field.type === LINK_TYPE && !field.tableId + $: invalid = + (field.type === LINK_TYPE && !field.tableId) || + Object.keys($backendUiStore.draftTable.schema).some( + key => key === field.name + ) // used to select what different options can be displayed for column type $: canBeSearched = diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index be93829084..0b98f2b256 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -15,18 +15,11 @@ } async function processFiles(fileList) { - const fileArray = Array.from(fileList) - - const filesToProcess = fileArray.map(({ name, path, size, type }) => ({ - name, - path, - size, - type, - })) - - const response = await api.post(`/api/attachments/process`, { - files: filesToProcess, - }) + let data = new FormData() + for (let i = 0; i < fileList.length; i++) { + data.append("file", fileList[i]) + } + const response = await api.post(`/api/attachments/process`, data, {}) return await response.json() } diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte index 6235e744f8..c777f79666 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte @@ -1,38 +1,42 @@ diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 7f1d73f744..5102179cde 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -123,13 +123,19 @@ async function createNewApp() { submitting = true try { - // Create App - const appResp = await post("/api/applications", { - name: $createAppStore.values.applicationName, - template, - }) - const appJson = await appResp.json() + // Create form data to create app + let data = new FormData() + data.append("name", $createAppStore.values.applicationName) + data.append("useTemplate", template != null) + if (template) { + data.append("templateName", template.name) + data.append("templateKey", template.key) + data.append("templateFile", template.file) + } + // Create App + const appResp = await post("/api/applications", data, {}) + const appJson = await appResp.json() if (!appResp.ok) { throw new Error(appJson.message) } diff --git a/packages/builder/src/components/start/Steps/Info.svelte b/packages/builder/src/components/start/Steps/Info.svelte index e14e327bce..fa74e8b3a0 100644 --- a/packages/builder/src/components/start/Steps/Info.svelte +++ b/packages/builder/src/components/start/Steps/Info.svelte @@ -1,6 +1,5 @@ diff --git a/packages/cli/package.json b/packages/cli/package.json index a957e8415d..5c58c80560 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.8.4", + "version": "0.8.7", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": "src/index.js", diff --git a/packages/client/package.json b/packages/client/package.json index 49ece2ed43..192bacfbc4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.8.5", + "version": "0.8.9", "license": "MPL-2.0", "main": "dist/budibase-client.js", "module": "dist/budibase-client.js", @@ -9,14 +9,13 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.8.5", - "deep-equal": "^2.0.1", + "@budibase/string-templates": "^0.8.9", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.8.5", + "@budibase/standard-components": "^0.8.9", "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "fs-extra": "^8.1.0", @@ -30,5 +29,5 @@ "svelte": "^3.30.0", "svelte-jester": "^1.0.6" }, - "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" + "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" } diff --git a/packages/client/src/store/notification.js b/packages/client/src/store/notification.js index d69a39080e..3efd40a882 100644 --- a/packages/client/src/store/notification.js +++ b/packages/client/src/store/notification.js @@ -13,8 +13,17 @@ const createNotificationStore = () => { _notifications.set([]) } }) + let block = false + + const blockNotifications = (timeout = 1000) => { + block = true + setTimeout(() => (block = false), timeout) + } const send = (message, type = "default") => { + if (block) { + return + } let _id = id() _notifications.update(state => { return [...state, { id: _id, type, message }] @@ -36,6 +45,7 @@ const createNotificationStore = () => { warning: msg => send(msg, "warning"), info: msg => send(msg, "info"), success: msg => send(msg, "success"), + blockNotifications, } } diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index b82ea3d13d..bcef9d320d 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -150,11 +150,6 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -182,13 +177,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -507,26 +495,6 @@ decimal.js@^10.2.0: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== -deep-equal@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0" - integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA== - dependencies: - es-abstract "^1.17.5" - es-get-iterator "^1.1.0" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.0.5" - isarray "^2.0.5" - object-is "^1.1.2" - object-keys "^1.1.1" - object.assign "^4.1.0" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.1" - which-typed-array "^1.1.2" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -544,13 +512,6 @@ deferred-leveldown@~0.2.0: dependencies: abstract-leveldown "~0.12.1" -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -615,63 +576,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0: - version "1.18.0-next.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" - integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-get-iterator@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -762,7 +666,7 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -foreach@^2.0.5, foreach@~2.0.1: +foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= @@ -859,11 +763,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -956,31 +855,11 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== - -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - is-core-module@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" @@ -988,31 +867,11 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-date-object@^1.0.1, is-date-object@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= - -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - is-object@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" @@ -1030,55 +889,11 @@ is-reference@^1.2.1: dependencies: "@types/estree" "*" -is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== - dependencies: - has-symbols "^1.0.1" - -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== - dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" - foreach "^2.0.5" - has-symbols "^1.0.1" - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - is@~0.2.6: version "0.2.7" resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" @@ -1089,11 +904,6 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1390,24 +1200,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-inspect@^1.7.0, object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-is@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - object-keys@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.2.0.tgz#cddec02998b091be42bf1035ae32e49f1cb6ea67" @@ -1422,16 +1214,6 @@ object-keys@~0.4.0: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - octal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/octal/-/octal-1.0.0.tgz#63e7162a68efbeb9e213588d58e989d1e5c4530b" @@ -1634,14 +1416,6 @@ readable-stream@~1.0.26, readable-stream@~1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - regexparam@1.3.0, regexparam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" @@ -1830,14 +1604,6 @@ shortid@^2.2.15: dependencies: nanoid "^2.1.0" -side-channel@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== - dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" - source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -1886,22 +1652,6 @@ string-range@~1.2, string-range@~1.2.1: resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd" integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0= -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2102,39 +1852,6 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^6.1.0" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== - dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - -which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== - dependencies: - available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" diff --git a/packages/server/__mocks__/@sendgrid/mail.js b/packages/server/__mocks__/@sendgrid/mail.js new file mode 100644 index 0000000000..e162237ff4 --- /dev/null +++ b/packages/server/__mocks__/@sendgrid/mail.js @@ -0,0 +1,18 @@ +class Email { + constructor() { + this.apiKey = null + } + + setApiKey(apiKey) { + this.apiKey = apiKey + } + + async send(msg) { + if (msg.to === "invalid@test.com") { + throw "Invalid" + } + return msg + } +} + +module.exports = new Email() diff --git a/packages/server/__mocks__/node-fetch.js b/packages/server/__mocks__/node-fetch.js index 1113791ec2..3cc412b1c6 100644 --- a/packages/server/__mocks__/node-fetch.js +++ b/packages/server/__mocks__/node-fetch.js @@ -1,17 +1,35 @@ const fetch = jest.requireActual("node-fetch") module.exports = async (url, opts) => { - // mocked data based on url - if (url.includes("api/apps")) { + function json(body, status = 200) { return { + status, json: async () => { - return { - app1: { - url: "/app1", - }, - } + return body }, } } + + // mocked data based on url + if (url.includes("api/apps")) { + return json({ + app1: { + url: "/app1", + }, + }) + } else if (url.includes("test.com")) { + return json({ + body: opts.body, + url, + method: opts.method, + }) + } else if (url.includes("invalid.com")) { + return json( + { + invalid: true, + }, + 404 + ) + } return fetch(url, opts) } diff --git a/packages/server/package.json b/packages/server/package.json index 99763109f3..6f52f1ac36 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.8.5", + "version": "0.8.9", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -33,7 +33,7 @@ }, "scripts": { "test": "jest --testPathIgnorePatterns=routes && npm run test:integration", - "test:integration": "jest --runInBand --coverage", + "test:integration": "jest --coverage --detectOpenHandles", "test:watch": "jest --watch", "run:docker": "node src/index", "dev:builder": "cross-env PORT=4001 nodemon src/index.js", @@ -53,11 +53,16 @@ "src/**/*.js", "!**/node_modules/**", "!src/db/views/*.js", - "!src/api/routes/tests/**/*.js", "!src/api/controllers/deploy/**/*.js", - "!src/api/controllers/static/templates/**/*", - "!src/api/controllers/static/selfhost/**/*", - "!src/*.js" + "!src/*.js", + "!src/api/controllers/static/**/*", + "!src/db/dynamoClient.js", + "!src/utilities/usageQuota.js", + "!src/api/routes/tests/**/*", + "!src/tests/**/*", + "!src/automations/tests/**/*", + "!src/utilities/fileProcessor.js", + "!src/utilities/initialiseBudibase.js" ], "coverageReporters": [ "lcov", @@ -71,8 +76,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/client": "^0.8.5", - "@budibase/string-templates": "^0.8.5", + "@budibase/client": "^0.8.9", + "@budibase/string-templates": "^0.8.9", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -126,7 +131,7 @@ "zlib": "1.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.8.5", + "@budibase/standard-components": "^0.8.9", "@jest/test-sequencer": "^24.8.0", "cross-env": "^7.0.3", "electron": "10.1.3", @@ -138,5 +143,5 @@ "pouchdb-adapter-memory": "^7.2.1", "supertest": "^4.0.2" }, - "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" + "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" } diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index cba3e6455a..9e50319d5d 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -91,7 +91,6 @@ async function getAppUrlIfNotInUse(ctx) { async function createInstance(template) { const appId = generateAppID() - const db = new CouchDB(appId) await db.put({ _id: "_design/database", @@ -106,10 +105,10 @@ async function createInstance(template) { // replicate the template data to the instance DB // this is currently very hard to test, downloading and importing template files /* istanbul ignore next */ - if (template) { + if (template && template.useTemplate === "true") { let dbDumpReadStream - if (template.fileImportPath) { - dbDumpReadStream = fs.createReadStream(template.fileImportPath) + if (template.file) { + dbDumpReadStream = fs.createReadStream(template.file.path) } else { const templatePath = await downloadTemplate(...template.key.split("/")) dbDumpReadStream = fs.createReadStream( @@ -162,8 +161,17 @@ exports.fetchAppPackage = async function(ctx) { } exports.create = async function(ctx) { + const { useTemplate, templateKey } = ctx.request.body + const instanceConfig = { + useTemplate, + key: templateKey, + } + if (ctx.request.files && ctx.request.files.templateFile) { + instanceConfig.file = ctx.request.files.templateFile + } + const instance = await createInstance(instanceConfig) + const url = await getAppUrlIfNotInUse(ctx) - const instance = await createInstance(ctx.request.body.template) const appId = instance._id const version = packageJson.version const newApplication = { diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index bf01314795..ff1ecf5b2e 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -152,26 +152,6 @@ async function processLocalFileUploads({ files, outputPath, appId }) { return processedFiles } -exports.performLocalFileProcessing = async function(ctx) { - const { files } = ctx.request.body - - const processedFileOutputPath = resolve( - budibaseAppsDir(), - ctx.user.appId, - "attachments" - ) - - try { - ctx.body = await processLocalFileUploads({ - files, - outputPath: processedFileOutputPath, - appId: ctx.user.appId, - }) - } catch (err) { - ctx.throw(500, err) - } -} - exports.serveApp = async function(ctx) { let appId = ctx.params.appId if (env.SELF_HOSTED) { diff --git a/packages/server/src/api/controllers/table/index.js b/packages/server/src/api/controllers/table/index.js index 93f4ec9f94..4cb1d16146 100644 --- a/packages/server/src/api/controllers/table/index.js +++ b/packages/server/src/api/controllers/table/index.js @@ -65,12 +65,14 @@ exports.save = async function(ctx) { // Don't rename if the name is the same let { _rename } = tableToSave + /* istanbul ignore next */ if (_rename && _rename.old === _rename.updated) { _rename = null delete tableToSave._rename } // rename row fields when table column is renamed + /* istanbul ignore next */ if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) { ctx.throw(400, "Cannot rename a linked column.") } else if (_rename && tableToSave.primaryDisplay === _rename.old) { @@ -159,7 +161,7 @@ exports.destroy = async function(ctx) { ctx.eventEmitter && ctx.eventEmitter.emitTable(`table:delete`, appId, tableToDelete) ctx.status = 200 - ctx.message = `Table ${ctx.params.tableId} deleted.` + ctx.body = { message: `Table ${ctx.params.tableId} deleted.` } } exports.validateCSVSchema = async function(ctx) { diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js index 73e6e60551..66b3651ccf 100644 --- a/packages/server/src/api/controllers/table/utils.js +++ b/packages/server/src/api/controllers/table/utils.js @@ -90,7 +90,8 @@ exports.handleDataImport = async (user, table, dataImport) => { return table } -exports.handleSearchIndexes = async (db, table) => { +exports.handleSearchIndexes = async (appId, table) => { + const db = new CouchDB(appId) // create relevant search indexes if (table.indexes && table.indexes.length > 0) { const currentIndexes = await db.getIndexes() @@ -150,6 +151,9 @@ class TableSaveFunctions { constructor({ db, ctx, oldTable, dataImport }) { this.db = db this.ctx = ctx + if (this.ctx && this.ctx.user) { + this.appId = this.ctx.user.appId + } this.oldTable = oldTable this.dataImport = dataImport // any rows that need updated @@ -178,7 +182,7 @@ class TableSaveFunctions { // after saving async after(table) { - table = await exports.handleSearchIndexes(this.db, table) + table = await exports.handleSearchIndexes(this.appId, table) table = await exports.handleDataImport( this.ctx.user, table, diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index 05dc299754..f482f3f2a6 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -29,11 +29,13 @@ const controller = { save: async ctx => { const db = new CouchDB(ctx.user.appId) const { originalName, ...viewToSave } = ctx.request.body - const designDoc = await db.get("_design/database") - const view = viewTemplate(viewToSave) + if (!viewToSave.name) { + ctx.throw(400, "Cannot create view without a name") + } + designDoc.views = { ...designDoc.views, [viewToSave.name]: view, @@ -60,17 +62,16 @@ const controller = { await db.put(table) - ctx.body = table.views[viewToSave.name] - ctx.message = `View ${viewToSave.name} saved successfully.` + ctx.body = { + ...table.views[viewToSave.name], + name: viewToSave.name, + } }, destroy: async ctx => { const db = new CouchDB(ctx.user.appId) const designDoc = await db.get("_design/database") - const viewName = decodeURI(ctx.params.viewName) - const view = designDoc.views[viewName] - delete designDoc.views[viewName] await db.put(designDoc) @@ -80,16 +81,17 @@ const controller = { await db.put(table) ctx.body = view - ctx.message = `View ${ctx.params.viewName} saved successfully.` }, exportView: async ctx => { const db = new CouchDB(ctx.user.appId) const designDoc = await db.get("_design/database") - const viewName = decodeURI(ctx.query.view) const view = designDoc.views[viewName] const format = ctx.query.format + if (!format) { + ctx.throw(400, "Format must be specified, either csv or json") + } if (view) { ctx.params.viewName = viewName @@ -102,6 +104,7 @@ const controller = { } } else { // table all_ view + /* istanbul ignore next */ ctx.params.viewName = viewName } diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index c812c4d3b1..30701d578b 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -29,11 +29,7 @@ if (env.SELF_HOSTED) { } router - .post( - "/api/attachments/process", - authorized(BUILDER), - controller.performLocalFileProcessing - ) + .post("/api/attachments/process", authorized(BUILDER), controller.uploadFile) .post("/api/attachments/upload", usage, controller.uploadFile) .get("/componentlibrary", controller.serveComponentLibrary) .get("/assets/:file*", controller.serveAppAsset) diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 9d11219506..5654c14c17 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -3,8 +3,8 @@ const { getAllTableRows, clearAllAutomations, } = require("./utilities/TestFunctions") -const { basicAutomation } = require("./utilities/structures") const setup = require("./utilities") +const { basicAutomation } = setup.structures const MAX_RETRIES = 4 diff --git a/packages/server/src/api/routes/tests/component.spec.js b/packages/server/src/api/routes/tests/component.spec.js index 926efc51e3..cabf9f8223 100644 --- a/packages/server/src/api/routes/tests/component.spec.js +++ b/packages/server/src/api/routes/tests/component.spec.js @@ -17,11 +17,12 @@ describe("/component", () => { function mock() { const manifestFile = "manifest.json" const appId = config.getAppId() - const libraries = ["@budibase/standard-components"] + const libraries = [join("@budibase", "standard-components")] for (let library of libraries) { let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package") fs.mkdirSync(appDirectory, { recursive: true }) - const file = require.resolve(library).split("dist/index.js")[0] + manifestFile + + const file = require.resolve(library).split(join("dist", "index.js"))[0] + manifestFile fs.copyFileSync(file, join(appDirectory, manifestFile)) } } diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js index ee1a1c47f5..c1448894b1 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.js @@ -1,6 +1,6 @@ -let {basicDatasource} = require("./utilities/structures") -let {checkBuilderEndpoint} = require("./utilities/TestFunctions") let setup = require("./utilities") +let { basicDatasource } = setup.structures +let { checkBuilderEndpoint } = require("./utilities/TestFunctions") describe("/datasources", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/layout.spec.js b/packages/server/src/api/routes/tests/layout.spec.js index 6b21554d71..4842b2cc8e 100644 --- a/packages/server/src/api/routes/tests/layout.spec.js +++ b/packages/server/src/api/routes/tests/layout.spec.js @@ -1,6 +1,6 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -const { basicLayout } = require("./utilities/structures") +const { basicLayout } = setup.structures describe("/layouts", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.js index 3d3b6047e2..2957e42d90 100644 --- a/packages/server/src/api/routes/tests/misc.spec.js +++ b/packages/server/src/api/routes/tests/misc.spec.js @@ -1,6 +1,7 @@ const setup = require("./utilities") +const tableUtils = require("../../controllers/table/utils") -describe("/analytics", () => { +describe("run misc tests", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -10,29 +11,44 @@ describe("/analytics", () => { await config.init() }) - describe("isEnabled", () => { - it("check if analytics enabled", async () => { - const res = await request - .get(`/api/analytics`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(typeof res.body.enabled).toEqual("boolean") + describe("/analytics", () => { + it("check if analytics enabled", async () => { + const res = await request + .get(`/api/analytics`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(typeof res.body.enabled).toEqual("boolean") + }) + }) + + describe("/health", () => { + it("should confirm healthy", async () => { + await request.get("/health").expect(200) }) }) -}) -describe("/health", () => { - it("should confirm healthy", async () => { - let config = setup.getConfig() - await config.getRequest().get("/health").expect(200) + describe("/version", () => { + it("should confirm version", async () => { + const res = await request.get("/version").expect(200) + expect(res.text.split(".").length).toEqual(3) + }) }) -}) -describe("/version", () => { - it("should confirm version", async () => { - const config = setup.getConfig() - const res = await config.getRequest().get("/version").expect(200) - expect(res.text.split(".").length).toEqual(3) + describe("test table utilities", () => { + it("should be able to import a CSV", async () => { + const table = await config.createTable() + const dataImport = { + csvString: "a,b,c,d\n1,2,3,4" + } + await tableUtils.handleDataImport({ + appId: config.getAppId(), + userId: "test", + }, table, dataImport) + const rows = await config.getRows() + expect(rows[0].a).toEqual("1") + expect(rows[0].b).toEqual("2") + expect(rows[0].c).toEqual("3") + }) }) }) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/permissions.spec.js b/packages/server/src/api/routes/tests/permissions.spec.js index b24fac57c0..aab5567881 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.js +++ b/packages/server/src/api/routes/tests/permissions.spec.js @@ -1,6 +1,6 @@ const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") const setup = require("./utilities") -const { basicRow } = require("./utilities/structures") +const { basicRow } = setup.structures const HIGHER_ROLE_ID = BUILTIN_ROLE_IDS.BASIC const STD_ROLE_ID = BUILTIN_ROLE_IDS.PUBLIC diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index aa0e5428c5..87938c6a37 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -1,9 +1,9 @@ // mock out postgres for this jest.mock("pg") -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { basicQuery, basicDatasource } = require("./utilities/structures") const setup = require("./utilities") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { basicQuery, basicDatasource } = setup.structures describe("/queries", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js index 9bb38b295a..062450cf63 100644 --- a/packages/server/src/api/routes/tests/role.spec.js +++ b/packages/server/src/api/routes/tests/role.spec.js @@ -2,8 +2,8 @@ const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") const { BUILTIN_PERMISSION_IDS, } = require("../../../utilities/security/permissions") -const { basicRole } = require("./utilities/structures") const setup = require("./utilities") +const { basicRole } = setup.structures describe("/roles", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index 70d1632bf3..beb1659b2a 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -1,5 +1,5 @@ const setup = require("./utilities") -const { basicScreen } = require("./utilities/structures") +const { basicScreen } = setup.structures const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 1442e4eb75..652a17366d 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -1,7 +1,6 @@ const { outputProcessing } = require("../../../utilities/rowProcessor") -const env = require("../../../environment") -const { basicRow } = require("./utilities/structures") const setup = require("./utilities") +const { basicRow } = setup.structures describe("/rows", () => { let request = setup.getRequest() @@ -349,7 +348,7 @@ describe("/rows", () => { const view = await config.createView() const row = await config.createRow() const res = await request - .get(`/api/views/${view._id}`) + .get(`/api/views/${view.name}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) diff --git a/packages/server/src/api/routes/tests/screen.spec.js b/packages/server/src/api/routes/tests/screen.spec.js index ae30afd29c..5533bc5e59 100644 --- a/packages/server/src/api/routes/tests/screen.spec.js +++ b/packages/server/src/api/routes/tests/screen.spec.js @@ -1,6 +1,6 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -const { basicScreen } = require("./utilities/structures") +const { basicScreen } = setup.structures describe("/screens", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.js index 1a2df624f1..df28eed0c2 100644 --- a/packages/server/src/api/routes/tests/table.spec.js +++ b/packages/server/src/api/routes/tests/table.spec.js @@ -1,5 +1,6 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { checkBuilderEndpoint, getDB } = require("./utilities/TestFunctions") const setup = require("./utilities") +const { basicTable } = setup.structures describe("/tables", () => { let request = setup.getRequest() @@ -12,25 +13,22 @@ describe("/tables", () => { }) describe("create", () => { - it("returns a success message when the table is successfully created", done => { - request + it("returns a success message when the table is successfully created", async () => { + const res = await request .post(`/api/tables`) - .send({ + .send({ name: "TestTable", key: "name", schema: { - name: { type: "string" } + name: {type: "string"} } }) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (err, res) => { - expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.") - expect(res.body.name).toEqual("TestTable") - done() - }) - }) + expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect(res.body.name).toEqual("TestTable") + }) it("renames all the row fields for a table when a schema key is renamed", async () => { const testTable = await config.createTable() @@ -46,7 +44,7 @@ describe("/tables", () => { const updatedTable = await request .post(`/api/tables`) - .send({ + .send({ _id: testTable._id, _rev: testTable._rev, name: "TestTable", @@ -56,41 +54,40 @@ describe("/tables", () => { updated: "updatedName" }, schema: { - updatedName: { type: "string" } + updatedName: {type: "string"} } }) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) + expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect(updatedTable.body.name).toEqual("TestTable") - expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.") - expect(updatedTable.body.name).toEqual("TestTable") + const res = await request + .get(`/api/${testTable._id}/rows/${testRow.body._id}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) - const res = await request - .get(`/api/${testTable._id}/rows/${testRow.body._id}`) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) + expect(res.body.updatedName).toEqual("test") + expect(res.body.name).toBeUndefined() + }) - expect(res.body.updatedName).toEqual("test") - expect(res.body.name).toBeUndefined() - }) - - it("should apply authorization to endpoint", async () => { - await checkBuilderEndpoint({ - config, - method: "POST", - url: `/api/tables`, - body: { - name: "TestTable", - key: "name", - schema: { - name: { type: "string" } - } + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/tables`, + body: { + name: "TestTable", + key: "name", + schema: { + name: {type: "string"} } - }) + } }) }) + }) describe("fetch", () => { let testTable @@ -103,28 +100,91 @@ describe("/tables", () => { delete testTable._rev }) - it("returns all the tables for that instance in the response body", done => { - request + it("returns all the tables for that instance in the response body", async () => { + const res = await request .get(`/api/tables`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (_, res) => { - const fetchedTable = res.body[0] - expect(fetchedTable.name).toEqual(testTable.name) - expect(fetchedTable.type).toEqual("table") - done() - }) + const fetchedTable = res.body[0] + expect(fetchedTable.name).toEqual(testTable.name) + expect(fetchedTable.type).toEqual("table") }) it("should apply authorization to endpoint", async () => { - await checkBuilderEndpoint({ - config, - method: "GET", - url: `/api/tables`, - }) + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/tables`, }) }) + }) + + describe("indexing", () => { + it("should be able to create a table with indexes", async () => { + const db = getDB(config) + const indexCount = (await db.getIndexes()).total_rows + const table = basicTable() + table.indexes = ["name"] + const res = await request + .post(`/api/tables`) + .send(table) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body._id).toBeDefined() + expect(res.body._rev).toBeDefined() + expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1) + // update index to see what happens + table.indexes = ["name", "description"] + await request + .post(`/api/tables`) + .send({ + ...table, + _id: res.body._id, + _rev: res.body._rev, + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + // shouldn't have created a new index + expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1) + }) + }) + + describe("updating user table", () => { + it("should add roleId and email field when adjusting user table schema", async () => { + const res = await request + .post(`/api/tables`) + .send({ + ...basicTable(), + _id: "ta_users", + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.schema.email).toBeDefined() + expect(res.body.schema.roleId).toBeDefined() + }) + }) + + describe("validate csv", () => { + it("should be able to validate a CSV layout", async () => { + const res = await request + .post(`/api/tables/csv/validate`) + .send({ + csvString: "a,b,c,d\n1,2,3,4" + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.schema).toBeDefined() + expect(res.body.schema.a).toEqual({ + type: "string", + success: true, + }) + }) + }) describe("destroy", () => { let testTable @@ -137,19 +197,16 @@ describe("/tables", () => { delete testTable._rev }) - it("returns a success response when a table is deleted.", async done => { - request + it("returns a success response when a table is deleted.", async () => { + const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) - done() - }) - }) + expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) + }) - it("deletes linked references to the table after deletion", async done => { + it("deletes linked references to the table after deletion", async () => { const linkedTable = await config.createTable({ name: "LinkedTable", type: "table", @@ -171,18 +228,15 @@ describe("/tables", () => { }, }) - request + const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) - const dependentTable = await config.getTable(linkedTable._id) - expect(dependentTable.schema.TestTable).not.toBeDefined() - done() - }) - }) + expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) + const dependentTable = await config.getTable(linkedTable._id) + expect(dependentTable.schema.TestTable).not.toBeDefined() + }) it("should apply authorization to endpoint", async () => { await checkBuilderEndpoint({ @@ -191,6 +245,5 @@ describe("/tables", () => { url: `/api/tables/${testTable._id}/${testTable._rev}`, }) }) - }) }) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 5e7ec9e9d4..808f1a2622 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -1,7 +1,7 @@ const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") -const { basicUser } = require("./utilities/structures") const setup = require("./utilities") +const { basicUser } = setup.structures describe("/users", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 534119d279..313b9e63a8 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -1,5 +1,6 @@ const rowController = require("../../../controllers/row") const appController = require("../../../controllers/application") +const CouchDB = require("../../../../db") function Request(appId, params) { this.user = { appId } @@ -77,3 +78,7 @@ exports.checkPermissionsEndpoint = async ({ .set(failHeader) .expect(403) } + +exports.getDB = config => { + return new CouchDB(config.getAppId()) +} diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js deleted file mode 100644 index a4eb9ac9de..0000000000 --- a/packages/server/src/api/routes/tests/utilities/controllers.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - table: require("../../../controllers/table"), - row: require("../../../controllers/row"), - role: require("../../../controllers/role"), - perms: require("../../../controllers/permission"), - view: require("../../../controllers/view"), - app: require("../../../controllers/application"), - user: require("../../../controllers/user"), - automation: require("../../../controllers/automation"), - datasource: require("../../../controllers/datasource"), - query: require("../../../controllers/query"), - screen: require("../../../controllers/screen"), - webhook: require("../../../controllers/webhook"), - layout: require("../../../controllers/layout"), -} diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js index 7126f141e2..ed5c98cc48 100644 --- a/packages/server/src/api/routes/tests/utilities/index.js +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -1,4 +1,5 @@ -const TestConfig = require("./TestConfiguration") +const TestConfig = require("../../../../tests/utilities/TestConfiguration") +const structures = require("../../../../tests/utilities/structures") const env = require("../../../../environment") exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms)) @@ -51,3 +52,5 @@ exports.switchToCloudForFunction = async func => { throw error } } + +exports.structures = structures diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js index a80b09d3a0..3bfbacccbe 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.js @@ -29,9 +29,7 @@ describe("/views", () => { .expect("Content-Type", /json/) .expect(200) - expect(res.res.statusMessage).toEqual( - "View TestView saved successfully." - ) + expect(res.body.tableId).toBe(table._id) }) it("updates the table row with the new view metadata", async () => { @@ -46,10 +44,8 @@ describe("/views", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) + expect(res.body.tableId).toBe(table._id) - expect(res.res.statusMessage).toEqual( - "View TestView saved successfully." - ) const updatedTable = await config.getTable(table._id) expect(updatedTable.views).toEqual({ TestView: { @@ -173,4 +169,49 @@ describe("/views", () => { expect(res.body).toMatchSnapshot() }) }) + + describe("destroy", () => { + it("should be able to delete a view", async () => { + const table = await config.createTable() + const view = await config.createView() + const res = await request + .delete(`/api/views/${view.name}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.map).toBeDefined() + expect(res.body.meta.tableId).toEqual(table._id) + }) + }) + + describe("exportView", () => { + it("should be able to delete a view", async () => { + await config.createTable() + await config.createRow() + const view = await config.createView() + let res = await request + .get(`/api/views/export?view=${view.name}&format=json`) + .set(config.defaultHeaders()) + .expect(200) + let error + try { + const obj = JSON.parse(res.text) + expect(obj.length).toBe(1) + } catch (err) { + error = err + } + expect(error).toBeUndefined() + res = await request + .get(`/api/views/export?view=${view.name}&format=csv`) + .set(config.defaultHeaders()) + .expect(200) + // this shouldn't be JSON + try { + JSON.parse(res.text) + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + }) }) diff --git a/packages/server/src/api/routes/tests/webhook.spec.js b/packages/server/src/api/routes/tests/webhook.spec.js index 2bf5445a09..7fb7a26fc1 100644 --- a/packages/server/src/api/routes/tests/webhook.spec.js +++ b/packages/server/src/api/routes/tests/webhook.spec.js @@ -1,6 +1,6 @@ const setup = require("./utilities") const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { basicWebhook, basicAutomation } = require("./utilities/structures") +const { basicWebhook, basicAutomation } = setup.structures describe("/webhooks", () => { let request = setup.getRequest() diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 15e996cfe6..8bbea00474 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -9,7 +9,6 @@ const env = require("./environment") const eventEmitter = require("./events") const automations = require("./automations/index") const Sentry = require("@sentry/node") -const selfhost = require("./selfhost") const app = new Koa() @@ -66,11 +65,7 @@ module.exports = server.listen(env.PORT || 0, async () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) env._set("PORT", server.address().port) eventEmitter.emitPort(env.PORT) - automations.init() - // only init the self hosting DB info in the Pouch, not needed in self hosting prod - if (!env.CLOUD) { - await selfhost.init() - } + await automations.init() }) process.on("uncaughtException", err => { diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index ea88c2d1d6..ee57f5a109 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -37,10 +37,12 @@ let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY let MANIFEST = null +/* istanbul ignore next */ function buildBundleName(pkgName, version) { return `${pkgName}@${version}.min.js` } +/* istanbul ignore next */ async function downloadPackage(name, version, bundleName) { await download( `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`, @@ -49,6 +51,7 @@ async function downloadPackage(name, version, bundleName) { return require(join(AUTOMATION_DIRECTORY, bundleName)) } +/* istanbul ignore next */ module.exports.getAction = async function(actionName) { if (BUILTIN_ACTIONS[actionName] != null) { return BUILTIN_ACTIONS[actionName] @@ -96,5 +99,6 @@ module.exports.init = async function() { return MANIFEST } +// definitions will have downloaded ones added to it, while builtin won't module.exports.DEFINITIONS = BUILTIN_DEFINITIONS module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index a983495fb5..9aba399133 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -30,23 +30,22 @@ async function updateQuota(automation) { /** * This module is built purely to kick off the worker farm and manage the inputs/outputs */ -module.exports.init = function() { - actions.init().then(() => { - triggers.automationQueue.process(async job => { - try { - if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) { - job.data.automation.apiKey = await updateQuota(job.data.automation) - } - if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") { - await runWorker(job) - } else { - await singleThread(job) - } - } catch (err) { - console.error( - `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` - ) +module.exports.init = async function() { + await actions.init() + triggers.automationQueue.process(async job => { + try { + if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) { + job.data.automation.apiKey = await updateQuota(job.data.automation) } - }) + if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") { + await runWorker(job) + } else { + await singleThread(job) + } + } catch (err) { + console.error( + `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` + ) + } }) } diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index aeb75958f6..ef136e1131 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -59,15 +59,14 @@ module.exports.definition = { } module.exports.run = async function({ inputs, appId, apiKey, emitter }) { - // TODO: better logging of when actions are missed due to missing parameters if (inputs.row == null || inputs.row.tableId == null) { - return + return { + success: false, + response: { + message: "Invalid inputs", + }, + } } - inputs.row = await automationUtils.cleanUpRow( - appId, - inputs.row.tableId, - inputs.row - ) // have to clean up the row, remove the table from it const ctx = { params: { @@ -81,6 +80,11 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { } try { + inputs.row = await automationUtils.cleanUpRow( + appId, + inputs.row.tableId, + inputs.row + ) if (env.CLOUD) { await usage.update(apiKey, usage.Properties.ROW, 1) } diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 8edee38dee..ea4d60a04e 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -51,9 +51,13 @@ module.exports.definition = { } module.exports.run = async function({ inputs, appId, apiKey, emitter }) { - // TODO: better logging of when actions are missed due to missing parameters if (inputs.id == null || inputs.revision == null) { - return + return { + success: false, + response: { + message: "Invalid inputs", + }, + } } let ctx = { params: { diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js index 4286cd44e8..586e424cc4 100644 --- a/packages/server/src/automations/steps/filter.js +++ b/packages/server/src/automations/steps/filter.js @@ -12,6 +12,9 @@ const PrettyLogicConditions = { [LogicConditions.LESS_THAN]: "Less than", } +module.exports.LogicConditions = LogicConditions +module.exports.PrettyLogicConditions = PrettyLogicConditions + module.exports.definition = { name: "Filter", tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}", @@ -64,7 +67,7 @@ module.exports.run = async function filter({ inputs }) { value = Date.parse(value) field = Date.parse(field) } - let success + let success = false if (typeof field !== "object" && typeof value !== "object") { switch (condition) { case LogicConditions.EQUAL: @@ -79,8 +82,6 @@ module.exports.run = async function filter({ inputs }) { case LogicConditions.LESS_THAN: success = field < value break - default: - return } } else { success = false diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index 817ec424b2..ab8c747c58 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -87,6 +87,7 @@ module.exports.run = async function({ inputs }) { success: response.status === 200, } } catch (err) { + /* istanbul ignore next */ return { success: false, response: err, diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 3b83f961f5..a545662cf8 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -55,14 +55,14 @@ module.exports.definition = { module.exports.run = async function({ inputs, appId, emitter }) { if (inputs.rowId == null || inputs.row == null) { - return + return { + success: false, + response: { + message: "Invalid inputs", + }, + } } - inputs.row = await automationUtils.cleanUpRowById( - appId, - inputs.rowId, - inputs.row - ) // clear any falsy properties so that they aren't updated for (let propKey of Object.keys(inputs.row)) { if (!inputs.row[propKey] || inputs.row[propKey] === "") { @@ -73,7 +73,7 @@ module.exports.run = async function({ inputs, appId, emitter }) { // have to clean up the row, remove the table from it const ctx = { params: { - id: inputs.rowId, + rowId: inputs.rowId, }, request: { body: inputs.row, @@ -83,6 +83,11 @@ module.exports.run = async function({ inputs, appId, emitter }) { } try { + inputs.row = await automationUtils.cleanUpRowById( + appId, + inputs.rowId, + inputs.row + ) await rowController.patch(ctx) return { row: ctx.body, diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js new file mode 100644 index 0000000000..f4d3b4c865 --- /dev/null +++ b/packages/server/src/automations/tests/automation.spec.js @@ -0,0 +1,152 @@ +const automation = require("../index") +const usageQuota = require("../../utilities/usageQuota") +const thread = require("../thread") +const triggers = require("../triggers") +const { basicAutomation, basicTable } = require("../../tests/utilities/structures") +const { wait } = require("../../utilities") +const env = require("../../environment") +const { makePartial } = require("../../tests/utilities") +const { cleanInputValues } = require("../automationUtils") +const setup = require("./utilities") + +let workerJob + +jest.mock("../../utilities/usageQuota") +usageQuota.getAPIKey.mockReturnValue({ apiKey: "test" }) +jest.mock("../thread") +jest.spyOn(global.console, "error") +jest.mock("worker-farm", () => { + return () => { + const value = jest + .fn() + .mockReturnValueOnce(undefined) + .mockReturnValueOnce("Error") + return (input, callback) => { + workerJob = input + if (callback) { + callback(value()) + } + } + } +}) + +describe("Run through some parts of the automations system", () => { + let config = setup.getConfig() + + beforeEach(async () => { + await automation.init() + await config.init() + }) + + afterAll(setup.afterAll) + + it("should be able to init in builder", async () => { + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) + expect(workerJob).toBeUndefined() + expect(thread).toHaveBeenCalled() + }) + + it("should be able to init in cloud", async () => { + env.CLOUD = true + env.BUDIBASE_ENVIRONMENT = "PRODUCTION" + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) + // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined + expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1) + expect(workerJob).toBeDefined() + env.BUDIBASE_ENVIRONMENT = "JEST" + env.CLOUD = false + }) + + it("try error scenario", async () => { + env.CLOUD = true + env.BUDIBASE_ENVIRONMENT = "PRODUCTION" + // the second call will throw an error + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) + expect(console.error).toHaveBeenCalled() + env.BUDIBASE_ENVIRONMENT = "JEST" + env.CLOUD = false + }) + + it("should be able to check triggering row filling", async () => { + const automation = basicAutomation() + let table = basicTable() + table.schema.boolean = { + type: "boolean", + constraints: { + type: "boolean", + }, + } + table.schema.number = { + type: "number", + constraints: { + type: "number", + }, + } + table.schema.datetime = { + type: "datetime", + constraints: { + type: "datetime", + }, + } + table = await config.createTable(table) + automation.definition.trigger.inputs.tableId = table._id + const params = await triggers.fillRowOutput(automation, { appId: config.getAppId() }) + expect(params.row).toBeDefined() + const date = new Date(params.row.datetime) + expect(typeof params.row.name).toBe("string") + expect(typeof params.row.boolean).toBe("boolean") + expect(typeof params.row.number).toBe("number") + expect(date.getFullYear()).toBe(1970) + }) + + it("should check coercion", async () => { + const table = await config.createTable() + const automation = basicAutomation() + automation.definition.trigger.inputs.tableId = table._id + automation.definition.trigger.stepId = "APP" + automation.definition.trigger.inputs.fields = { a: "number" } + await triggers.externalTrigger(automation, { + appId: config.getAppId(), + fields: { + a: "1" + } + }) + await wait(100) + expect(thread).toHaveBeenCalledWith(makePartial({ + data: { + event: { + fields: { + a: 1 + } + } + } + })) + }) + + it("should be able to clean inputs with the utilities", () => { + // can't clean without a schema + let output = cleanInputValues({a: "1"}) + expect(output.a).toBe("1") + output = cleanInputValues({a: "1", b: "true", c: "false", d: 1, e: "help"}, { + properties: { + a: { + type: "number", + }, + b: { + type: "boolean", + }, + c: { + type: "boolean", + } + } + }) + expect(output.a).toBe(1) + expect(output.b).toBe(true) + expect(output.c).toBe(false) + expect(output.d).toBe(1) + expect(output.e).toBe("help") + }) +}) \ No newline at end of file diff --git a/packages/server/src/automations/tests/createRow.spec.js b/packages/server/src/automations/tests/createRow.spec.js new file mode 100644 index 0000000000..0be2803e47 --- /dev/null +++ b/packages/server/src/automations/tests/createRow.spec.js @@ -0,0 +1,57 @@ +const usageQuota = require("../../utilities/usageQuota") +const env = require("../../environment") +const setup = require("./utilities") + +jest.mock("../../utilities/usageQuota") + +describe("test the create row action", () => { + let table, row + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + table = await config.createTable() + row = { + tableId: table._id, + name: "test", + description: "test", + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row, + }) + expect(res.id).toBeDefined() + expect(res.revision).toBeDefined() + const gottenRow = await config.getRow(table._id, res.id) + expect(gottenRow.name).toEqual("test") + expect(gottenRow.description).toEqual("test") + }) + + it("should return an error (not throw) when bad info provided", async () => { + const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row: { + tableId: "invalid", + invalid: "invalid", + } + }) + expect(res.success).toEqual(false) + }) + + it("check usage quota attempts", async () => { + env.CLOUD = true + await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row + }) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1) + env.CLOUD = false + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) +}) diff --git a/packages/server/src/automations/tests/createUser.spec.js b/packages/server/src/automations/tests/createUser.spec.js new file mode 100644 index 0000000000..5f65e260a9 --- /dev/null +++ b/packages/server/src/automations/tests/createUser.spec.js @@ -0,0 +1,43 @@ +const usageQuota = require("../../utilities/usageQuota") +const env = require("../../environment") +const setup = require("./utilities") +const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles") +const { ViewNames } = require("../../db/utils") + +jest.mock("../../utilities/usageQuota") + +describe("test the create user action", () => { + let config = setup.getConfig() + let user + + beforeEach(async () => { + await config.init() + user = { + email: "test@test.com", + password: "password", + roleId: BUILTIN_ROLE_IDS.POWER + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.CREATE_USER.stepId, user) + expect(res.id).toBeDefined() + expect(res.revision).toBeDefined() + const userDoc = await config.getRow(ViewNames.USERS, res.id) + expect(userDoc.email).toEqual(user.email) + }) + + it("should return an error if no inputs provided", async () => { + const res = await setup.runStep(setup.actions.CREATE_USER.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("check usage quota attempts", async () => { + env.CLOUD = true + await setup.runStep(setup.actions.CREATE_USER.stepId, user) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1) + env.CLOUD = false + }) +}) diff --git a/packages/server/src/automations/tests/delay.spec.js b/packages/server/src/automations/tests/delay.spec.js new file mode 100644 index 0000000000..99046e8171 --- /dev/null +++ b/packages/server/src/automations/tests/delay.spec.js @@ -0,0 +1,12 @@ +const setup = require("./utilities") + +describe("test the delay logic", () => { + it("should be able to run the delay", async () => { + const time = 100 + const before = Date.now() + await setup.runStep(setup.logic.DELAY.stepId, { time: time }) + const now = Date.now() + // divide by two just so that test will always pass as long as there was some sort of delay + expect(now - before).toBeGreaterThanOrEqual(time / 2) + }) +}) \ No newline at end of file diff --git a/packages/server/src/automations/tests/deleteRow.spec.js b/packages/server/src/automations/tests/deleteRow.spec.js new file mode 100644 index 0000000000..0d5ff47ed8 --- /dev/null +++ b/packages/server/src/automations/tests/deleteRow.spec.js @@ -0,0 +1,58 @@ +const usageQuota = require("../../utilities/usageQuota") +const env = require("../../environment") +const setup = require("./utilities") + +jest.mock("../../utilities/usageQuota") + +describe("test the delete row action", () => { + let table, row, inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + table = await config.createTable() + row = await config.createRow() + inputs = { + tableId: table._id, + id: row._id, + revision: row._rev, + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(res.success).toEqual(true) + expect(res.response).toBeDefined() + expect(res.row._id).toEqual(row._id) + let error + try { + await config.getRow(table._id, res.id) + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + + it("check usage quota attempts", async () => { + env.CLOUD = true + await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) + env.CLOUD = false + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("should return an error when table doesn't exist", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, { + tableId: "invalid", + id: "invalid", + revision: "invalid", + }) + expect(res.success).toEqual(false) + }) +}) diff --git a/packages/server/src/automations/tests/filter.spec.js b/packages/server/src/automations/tests/filter.spec.js new file mode 100644 index 0000000000..05361f43ed --- /dev/null +++ b/packages/server/src/automations/tests/filter.spec.js @@ -0,0 +1,48 @@ +const setup = require("./utilities") +const { LogicConditions } = require("../steps/filter") + +describe("test the filter logic", () => { + async function checkFilter(field, condition, value, pass = true) { + let res = await setup.runStep(setup.logic.FILTER.stepId, + { field, condition, value } + ) + expect(res.success).toEqual(pass) + } + + it("should be able test equality", async () => { + await checkFilter("hello", LogicConditions.EQUAL, "hello", true) + await checkFilter("hello", LogicConditions.EQUAL, "no", false) + }) + + it("should be able to test greater than", async () => { + await checkFilter(10, LogicConditions.GREATER_THAN, 5, true) + await checkFilter(10, LogicConditions.GREATER_THAN, 15, false) + }) + + it("should be able to test less than", async () => { + await checkFilter(5, LogicConditions.LESS_THAN, 10, true) + await checkFilter(15, LogicConditions.LESS_THAN, 10, false) + }) + + it("should be able to in-equality", async () => { + await checkFilter("hello", LogicConditions.NOT_EQUAL, "no", true) + await checkFilter(10, LogicConditions.NOT_EQUAL, 10, false) + }) + + it("check number coercion", async () => { + await checkFilter("10", LogicConditions.GREATER_THAN, "5", true) + }) + + it("check date coercion", async () => { + await checkFilter( + (new Date()).toISOString(), + LogicConditions.GREATER_THAN, + (new Date(-10000)).toISOString(), + true + ) + }) + + it("check objects always false", async () => { + await checkFilter({}, LogicConditions.EQUAL, {}, false) + }) +}) \ No newline at end of file diff --git a/packages/server/src/automations/tests/outgoingWebhook.spec.js b/packages/server/src/automations/tests/outgoingWebhook.spec.js new file mode 100644 index 0000000000..f1d8d25ba8 --- /dev/null +++ b/packages/server/src/automations/tests/outgoingWebhook.spec.js @@ -0,0 +1,39 @@ +const setup = require("./utilities") +const fetch = require("node-fetch") + +jest.mock("node-fetch") + +describe("test the outgoing webhook action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + requestMethod: "POST", + url: "www.test.com", + requestBody: JSON.stringify({ + a: 1, + }), + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, inputs) + expect(res.success).toEqual(true) + expect(res.response.url).toEqual("http://www.test.com") + expect(res.response.method).toEqual("POST") + expect(res.response.body.a).toEqual(1) + }) + + it("should return an error if something goes wrong in fetch", async () => { + const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, { + requestMethod: "GET", + url: "www.invalid.com" + }) + expect(res.success).toEqual(false) + }) + +}) diff --git a/packages/server/src/automations/tests/sendEmail.spec.js b/packages/server/src/automations/tests/sendEmail.spec.js new file mode 100644 index 0000000000..5f3aabfff8 --- /dev/null +++ b/packages/server/src/automations/tests/sendEmail.spec.js @@ -0,0 +1,36 @@ +const setup = require("./utilities") + +jest.mock("@sendgrid/mail") + +describe("test the send email action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + to: "me@test.com", + from: "budibase@test.com", + subject: "Testing", + text: "Email contents", + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, inputs) + expect(res.success).toEqual(true) + // the mocked module throws back the input + expect(res.response.to).toEqual("me@test.com") + }) + + it("should return an error if input an invalid email address", async () => { + const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, { + ...inputs, + to: "invalid@test.com", + }) + expect(res.success).toEqual(false) + }) + +}) diff --git a/packages/server/src/automations/tests/updateRow.spec.js b/packages/server/src/automations/tests/updateRow.spec.js new file mode 100644 index 0000000000..79c998459b --- /dev/null +++ b/packages/server/src/automations/tests/updateRow.spec.js @@ -0,0 +1,45 @@ +const env = require("../../environment") +const setup = require("./utilities") + +describe("test the update row action", () => { + let table, row, inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + table = await config.createTable() + row = await config.createRow() + inputs = { + rowId: row._id, + row: { + ...row, + name: "Updated name", + // put a falsy option in to be removed + description: "", + } + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs) + expect(res.success).toEqual(true) + const updatedRow = await config.getRow(table._id, res.id) + expect(updatedRow.name).toEqual("Updated name") + expect(updatedRow.description).not.toEqual("") + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("should return an error when table doesn't exist", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { + row: { _id: "invalid" }, + rowId: "invalid", + }) + expect(res.success).toEqual(false) + }) +}) diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js new file mode 100644 index 0000000000..ad149d6bde --- /dev/null +++ b/packages/server/src/automations/tests/utilities/index.js @@ -0,0 +1,43 @@ +const TestConfig = require("../../../tests/utilities/TestConfiguration") +const actions = require("../../actions") +const logic = require("../../logic") +const emitter = require("../../../events/index") + +let config + +exports.getConfig = () => { + if (!config) { + config = new TestConfig(false) + } + return config +} + +exports.afterAll = () => { + config.end() +} + +exports.runStep = async function runStep(stepId, inputs) { + let step + if ( + Object.values(exports.actions) + .map(action => action.stepId) + .includes(stepId) + ) { + step = await actions.getAction(stepId) + } else { + step = logic.getLogic(stepId) + } + expect(step).toBeDefined() + return step({ + inputs, + appId: config ? config.getAppId() : null, + // don't really need an API key, mocked out usage quota, not being tested here + apiKey: exports.apiKey, + emitter, + }) +} + +exports.apiKey = "test" + +exports.actions = actions.BUILTIN_DEFINITIONS +exports.logic = logic.BUILTIN_DEFINITIONS diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 73ce9edeed..7e50e5ee74 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -225,6 +225,7 @@ async function queueRelevantRowAutomations(event, eventType) { } emitter.on("row:save", async function(event) { + /* istanbul ignore next */ if (!event || !event.row || !event.row.tableId) { return } @@ -232,6 +233,7 @@ emitter.on("row:save", async function(event) { }) emitter.on("row:update", async function(event) { + /* istanbul ignore next */ if (!event || !event.row || !event.row.tableId) { return } @@ -239,6 +241,7 @@ emitter.on("row:update", async function(event) { }) emitter.on("row:delete", async function(event) { + /* istanbul ignore next */ if (!event || !event.row || !event.row.tableId) { return } @@ -272,6 +275,7 @@ async function fillRowOutput(automation, params) { } params.row = row } catch (err) { + /* istanbul ignore next */ throw "Failed to find table for trigger" } return params @@ -297,6 +301,7 @@ module.exports.externalTrigger = async function(automation, params) { automationQueue.add({ automation, event: params }) } +module.exports.fillRowOutput = fillRowOutput module.exports.automationQueue = automationQueue module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index b5edb1e877..f6dea33a40 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -30,6 +30,7 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS) allDbs(Pouch) // replicate your local levelDB pouch to a running HTTP compliant couch or pouchdb server. +/* istanbul ignore next */ // eslint-disable-next-line no-unused-vars function replicateLocal() { Pouch.allDbs().then(dbs => { diff --git a/packages/server/src/middleware/tests/usageQuota.spec.js b/packages/server/src/middleware/tests/usageQuota.spec.js index c76acb47d2..395f14c1ed 100644 --- a/packages/server/src/middleware/tests/usageQuota.spec.js +++ b/packages/server/src/middleware/tests/usageQuota.spec.js @@ -3,7 +3,7 @@ const usageQuota = require("../../utilities/usageQuota") const CouchDB = require("../../db") const env = require("../../environment") -jest.mock("../../db"); +jest.mock("../../db") jest.mock("../../utilities/usageQuota") jest.mock("../../environment") diff --git a/packages/server/src/selfhost/README.md b/packages/server/src/selfhost/README.md deleted file mode 100644 index a02743a58c..0000000000 --- a/packages/server/src/selfhost/README.md +++ /dev/null @@ -1,7 +0,0 @@ -### Self hosting -This directory contains utilities that are needed for self hosted platforms to operate. -These will mostly be utilities, necessary to the operation of the server e.g. storing self -hosting specific options and attributes to CouchDB. - -All the internal operations should be exposed through the `index.js` so importing -the self host directory should give you everything you need. \ No newline at end of file diff --git a/packages/server/src/selfhost/index.js b/packages/server/src/selfhost/index.js deleted file mode 100644 index f77d1f0b6c..0000000000 --- a/packages/server/src/selfhost/index.js +++ /dev/null @@ -1,44 +0,0 @@ -const CouchDB = require("../db") -const env = require("../environment") -const newid = require("../db/newid") - -const SELF_HOST_DB = "self-host-db" -const SELF_HOST_DOC = "self-host-info" - -async function createSelfHostDB(db) { - await db.put({ - _id: "_design/database", - views: {}, - }) - const selfHostInfo = { - _id: SELF_HOST_DOC, - apiKeyId: newid(), - } - await db.put(selfHostInfo) - return selfHostInfo -} - -exports.init = async () => { - if (!env.SELF_HOSTED) { - return - } - const db = new CouchDB(SELF_HOST_DB) - try { - await db.get(SELF_HOST_DOC) - } catch (err) { - // failed to retrieve - if (err.status === 404) { - await createSelfHostDB(db) - } - } -} - -exports.getSelfHostInfo = async () => { - const db = new CouchDB(SELF_HOST_DB) - return db.get(SELF_HOST_DOC) -} - -exports.getSelfHostAPIKey = async () => { - const info = await exports.getSelfHostInfo() - return info ? info.apiKeyId : null -} diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js similarity index 88% rename from packages/server/src/api/routes/tests/utilities/TestConfiguration.js rename to packages/server/src/tests/utilities/TestConfiguration.js index 0ff742293d..b36b45186a 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -1,6 +1,6 @@ -const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles") const jwt = require("jsonwebtoken") -const env = require("../../../../environment") +const env = require("../../environment") const { basicTable, basicRow, @@ -15,18 +15,20 @@ const { const controllers = require("./controllers") const supertest = require("supertest") const fs = require("fs") -const { budibaseAppsDir } = require("../../../../utilities/budibaseDir") +const { budibaseAppsDir } = require("../../utilities/budibaseDir") const { join } = require("path") const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" class TestConfiguration { - constructor() { - env.PORT = 4002 - this.server = require("../../../../app") - // we need the request for logging in, involves cookies, hard to fake - this.request = supertest(this.server) + constructor(openServer = true) { + if (openServer) { + env.PORT = 4002 + this.server = require("../../app") + // we need the request for logging in, involves cookies, hard to fake + this.request = supertest(this.server) + } this.appId = null this.allApps = [] } @@ -61,7 +63,9 @@ class TestConfiguration { } end() { - this.server.close() + if (this.server) { + this.server.close() + } const appDir = budibaseAppsDir() const files = fs.readdirSync(appDir) for (let file of files) { @@ -163,6 +167,17 @@ class TestConfiguration { return this._req(config, { tableId: this.table._id }, controllers.row.save) } + async getRow(tableId, rowId) { + return this._req(null, { tableId, rowId }, controllers.row.find) + } + + async getRows(tableId) { + if (!tableId && this.table) { + tableId = this.table._id + } + return this._req(null, { tableId }, controllers.row.fetchTableRows) + } + async createRole(config = null) { config = config || basicRole() return this._req(config, null, controllers.role.save) @@ -187,6 +202,7 @@ class TestConfiguration { const view = config || { map: "function(doc) { emit(doc[doc.key], doc._id); } ", tableId: this.table._id, + name: "ViewTest", } return this._req(view, null, controllers.view.save) } @@ -285,6 +301,9 @@ class TestConfiguration { } async login(email, password) { + if (!this.request) { + throw "Server has not been opened, cannot login." + } if (!email || !password) { await this.createUser() email = EMAIL diff --git a/packages/server/src/tests/utilities/controllers.js b/packages/server/src/tests/utilities/controllers.js new file mode 100644 index 0000000000..b07754038f --- /dev/null +++ b/packages/server/src/tests/utilities/controllers.js @@ -0,0 +1,15 @@ +module.exports = { + table: require("../../api/controllers/table"), + row: require("../../api/controllers/row"), + role: require("../../api/controllers/role"), + perms: require("../../api/controllers/permission"), + view: require("../../api/controllers/view"), + app: require("../../api/controllers/application"), + user: require("../../api/controllers/user"), + automation: require("../../api/controllers/automation"), + datasource: require("../../api/controllers/datasource"), + query: require("../../api/controllers/query"), + screen: require("../../api/controllers/screen"), + webhook: require("../../api/controllers/webhook"), + layout: require("../../api/controllers/layout"), +} diff --git a/packages/server/src/tests/utilities/index.js b/packages/server/src/tests/utilities/index.js new file mode 100644 index 0000000000..aa8039ce2f --- /dev/null +++ b/packages/server/src/tests/utilities/index.js @@ -0,0 +1,11 @@ +exports.makePartial = obj => { + const newObj = {} + for (let key of Object.keys(obj)) { + if (typeof obj[key] === "object") { + newObj[key] = exports.makePartial(obj[key]) + } else { + newObj[key] = obj[key] + } + } + return expect.objectContaining(newObj) +} diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/tests/utilities/structures.js similarity index 86% rename from packages/server/src/api/routes/tests/utilities/structures.js rename to packages/server/src/tests/utilities/structures.js index ff3a239211..e6489f0903 100644 --- a/packages/server/src/api/routes/tests/utilities/structures.js +++ b/packages/server/src/tests/utilities/structures.js @@ -1,9 +1,9 @@ -const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles") const { BUILTIN_PERMISSION_IDS, -} = require("../../../../utilities/security/permissions") -const { createHomeScreen } = require("../../../../constants/screens") -const { EMPTY_LAYOUT } = require("../../../../constants/layouts") +} = require("../../utilities/security/permissions") +const { createHomeScreen } = require("../../constants/screens") +const { EMPTY_LAYOUT } = require("../../constants/layouts") const { cloneDeep } = require("lodash/fp") exports.basicTable = () => { diff --git a/packages/server/src/utilities/createAppPackage.js b/packages/server/src/utilities/createAppPackage.js index a62e8c96df..9500554227 100644 --- a/packages/server/src/utilities/createAppPackage.js +++ b/packages/server/src/utilities/createAppPackage.js @@ -7,6 +7,8 @@ const packageJson = require("../../package.json") const streamPipeline = promisify(stream.pipeline) +// can't really test this due to the downloading nature of it, wouldn't be a great test case +/* istanbul ignore next */ exports.downloadExtractComponentLibraries = async appFolder => { const LIBRARIES = ["standard-components"] diff --git a/packages/server/src/utilities/exceptions.js b/packages/server/src/utilities/exceptions.js deleted file mode 100644 index e02c88eec3..0000000000 --- a/packages/server/src/utilities/exceptions.js +++ /dev/null @@ -1,16 +0,0 @@ -const statusCodes = require("./statusCodes") - -const errorWithStatus = (message, statusCode) => { - const e = new Error(message) - e.statusCode = statusCode - return e -} - -module.exports.unauthorized = message => - errorWithStatus(message, statusCodes.UNAUTHORIZED) - -module.exports.forbidden = message => - errorWithStatus(message, statusCodes.FORBIDDEN) - -module.exports.notfound = message => - errorWithStatus(message, statusCodes.NOT_FOUND) diff --git a/packages/server/src/utilities/routing/index.js b/packages/server/src/utilities/routing/index.js index f4af585dc6..541733dcc4 100644 --- a/packages/server/src/utilities/routing/index.js +++ b/packages/server/src/utilities/routing/index.js @@ -12,6 +12,7 @@ exports.getRoutingInfo = async appId => { return allRouting.rows.map(row => row.value) } catch (err) { // check if the view doesn't exist, it should for all new instances + /* istanbul ignore next */ if (err != null && err.name === "not_found") { await createRoutingView(appId) return exports.getRoutingInfo(appId) diff --git a/packages/server/src/utilities/security/apikey.js b/packages/server/src/utilities/security/apikey.js index c8965cee43..3d5f428bb7 100644 --- a/packages/server/src/utilities/security/apikey.js +++ b/packages/server/src/utilities/security/apikey.js @@ -1,6 +1,5 @@ const { apiKeyTable } = require("../../db/dynamoClient") const env = require("../../environment") -const { getSelfHostAPIKey } = require("../../selfhost") /** * This file purely exists so that we can centralise all logic pertaining to API keys, as their usage differs @@ -8,16 +7,13 @@ const { getSelfHostAPIKey } = require("../../selfhost") */ exports.isAPIKeyValid = async apiKeyId => { - if (env.CLOUD && !env.SELF_HOSTED) { + if (!env.SELF_HOSTED) { let apiKeyInfo = await apiKeyTable.get({ primary: apiKeyId, }) return apiKeyInfo != null - } - if (env.SELF_HOSTED) { - const selfHostKey = await getSelfHostAPIKey() + } else { // if the api key supplied is correct then return structure similar - return apiKeyId === selfHostKey ? { pk: apiKeyId } : null + return apiKeyId === env.HOSTING_KEY } - return false } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 8bce8b2f0b..3e937cff44 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -35,9 +35,9 @@ "keywords": [ "svelte" ], - "version": "0.8.5", + "version": "0.8.9", "license": "MIT", - "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504", + "gitHead": "1b95326b20d1352d36305910259228b96a683dc7", "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.1.0", "@budibase/bbui": "^1.58.13", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index ec7858da74..6e0b573656 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.8.5", + "version": "0.8.9", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.js", "module": "src/index.js", @@ -33,5 +33,5 @@ "rollup-plugin-terser": "^7.0.2", "typescript": "^4.1.3" }, - "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" + "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" } diff --git a/packages/worker/package.json b/packages/worker/package.json index 9f12a0ac1a..1670fd729e 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/deployment", "email": "hi@budibase.com", - "version": "0.8.5", + "version": "0.8.9", "description": "Budibase Deployment Server", "main": "src/index.js", "repository": { @@ -34,5 +34,5 @@ "pouchdb-all-dbs": "^1.0.2", "server-destroy": "^1.0.1" }, - "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" + "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" }