Browse Source

Merge branch 'next' into fix/route-preservation

pull/4023/head
Keviin Åberg Kultalahti 5 years ago
parent
commit
0b36b98295
  1. 2
      .github/workflows/budibase_ci.yml
  2. 4
      .github/workflows/release.yml
  3. 2
      README.md
  4. 2
      lerna.json
  5. 6
      packages/builder/package.json
  6. 3
      packages/builder/src/builderStore/api.js
  7. 6
      packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
  8. 17
      packages/builder/src/components/common/Dropzone.svelte
  9. 34
      packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte
  10. 16
      packages/builder/src/components/start/CreateAppModal.svelte
  11. 5
      packages/builder/src/components/start/Steps/Info.svelte
  12. 2
      packages/cli/package.json
  13. 9
      packages/client/package.json
  14. 10
      packages/client/src/store/notification.js
  15. 285
      packages/client/yarn.lock
  16. 18
      packages/server/__mocks__/@sendgrid/mail.js
  17. 32
      packages/server/__mocks__/node-fetch.js
  18. 25
      packages/server/package.json
  19. 18
      packages/server/src/api/controllers/application.js
  20. 20
      packages/server/src/api/controllers/static/index.js
  21. 4
      packages/server/src/api/controllers/table/index.js
  22. 8
      packages/server/src/api/controllers/table/utils.js
  23. 21
      packages/server/src/api/controllers/view/index.js
  24. 6
      packages/server/src/api/routes/static.js
  25. 2
      packages/server/src/api/routes/tests/automation.spec.js
  26. 5
      packages/server/src/api/routes/tests/component.spec.js
  27. 4
      packages/server/src/api/routes/tests/datasource.spec.js
  28. 2
      packages/server/src/api/routes/tests/layout.spec.js
  29. 56
      packages/server/src/api/routes/tests/misc.spec.js
  30. 2
      packages/server/src/api/routes/tests/permissions.spec.js
  31. 4
      packages/server/src/api/routes/tests/query.spec.js
  32. 2
      packages/server/src/api/routes/tests/role.spec.js
  33. 2
      packages/server/src/api/routes/tests/routing.spec.js
  34. 5
      packages/server/src/api/routes/tests/row.spec.js
  35. 2
      packages/server/src/api/routes/tests/screen.spec.js
  36. 183
      packages/server/src/api/routes/tests/table.spec.js
  37. 2
      packages/server/src/api/routes/tests/user.spec.js
  38. 5
      packages/server/src/api/routes/tests/utilities/TestFunctions.js
  39. 15
      packages/server/src/api/routes/tests/utilities/controllers.js
  40. 5
      packages/server/src/api/routes/tests/utilities/index.js
  41. 53
      packages/server/src/api/routes/tests/view.spec.js
  42. 2
      packages/server/src/api/routes/tests/webhook.spec.js
  43. 7
      packages/server/src/app.js
  44. 4
      packages/server/src/automations/actions.js
  45. 33
      packages/server/src/automations/index.js
  46. 18
      packages/server/src/automations/steps/createRow.js
  47. 8
      packages/server/src/automations/steps/deleteRow.js
  48. 7
      packages/server/src/automations/steps/filter.js
  49. 1
      packages/server/src/automations/steps/outgoingWebhook.js
  50. 19
      packages/server/src/automations/steps/updateRow.js
  51. 152
      packages/server/src/automations/tests/automation.spec.js
  52. 57
      packages/server/src/automations/tests/createRow.spec.js
  53. 43
      packages/server/src/automations/tests/createUser.spec.js
  54. 12
      packages/server/src/automations/tests/delay.spec.js
  55. 58
      packages/server/src/automations/tests/deleteRow.spec.js
  56. 48
      packages/server/src/automations/tests/filter.spec.js
  57. 39
      packages/server/src/automations/tests/outgoingWebhook.spec.js
  58. 36
      packages/server/src/automations/tests/sendEmail.spec.js
  59. 45
      packages/server/src/automations/tests/updateRow.spec.js
  60. 43
      packages/server/src/automations/tests/utilities/index.js
  61. 5
      packages/server/src/automations/triggers.js
  62. 1
      packages/server/src/db/client.js
  63. 2
      packages/server/src/middleware/tests/usageQuota.spec.js
  64. 7
      packages/server/src/selfhost/README.md
  65. 44
      packages/server/src/selfhost/index.js
  66. 37
      packages/server/src/tests/utilities/TestConfiguration.js
  67. 15
      packages/server/src/tests/utilities/controllers.js
  68. 11
      packages/server/src/tests/utilities/index.js
  69. 8
      packages/server/src/tests/utilities/structures.js
  70. 2
      packages/server/src/utilities/createAppPackage.js
  71. 16
      packages/server/src/utilities/exceptions.js
  72. 1
      packages/server/src/utilities/routing/index.js
  73. 10
      packages/server/src/utilities/security/apikey.js
  74. 4
      packages/standard-components/package.json
  75. 4
      packages/string-templates/package.json
  76. 4
      packages/worker/package.json

2
.github/workflows/budibase_ci.yml

@ -7,10 +7,12 @@ on:
branches:
- master
- develop
- next
pull_request:
branches:
- master
- develop
- next
jobs:
build:

4
.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') }}

2
README.md

@ -38,7 +38,7 @@
</a>
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" />
<a href="https://codecov.io/gh/Budibase/budibase">
<img src="https://codecov.io/gh/Budibase/budibase/branch/master/graph/badge.svg?token=E8W2ZFXQOH"/>
<img src="https://codecov.io/gh/Budibase/budibase/branch/next/graph/badge.svg?token=E8W2ZFXQOH"/>
</a>
</p>

2
lerna.json

@ -1,5 +1,5 @@
{
"version": "0.8.5",
"version": "0.8.9",
"npmClient": "yarn",
"packages": [
"packages/*"

6
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",

3
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,
})
}

6
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 =

17
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()
}
</script>

34
packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte

@ -1,38 +1,42 @@
<script>
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher } from "svelte"
import Colorpicker from "@budibase/colorpicker"
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
export let value
const WAIT = 150;
const WAIT = 150
function throttle(callback, wait, immediate = false) {
let timeout = null
let timeout = null
let initialCall = true
return function() {
const callNow = immediate && initialCall
const next = () => {
callback.apply(this, arguments)
timeout = null
}
if (callNow) {
if (callNow) {
initialCall = false
next()
}
if (!timeout) {
timeout = setTimeout(next, wait)
}
}
}
const onChange = throttle(e => {
dispatch('change', e.detail)
}, WAIT, true)
const onChange = throttle(
e => {
dispatch("change", e.detail)
},
WAIT,
true
)
</script>
<Colorpicker value={value || '#C4C4C4'} on:change={onChange} />

16
packages/builder/src/components/start/CreateAppModal.svelte

@ -123,13 +123,19 @@
async function createNewApp() {
submitting = true
try {
// 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", {
name: $createAppStore.values.applicationName,
template,
})
const appResp = await post("/api/applications", data, {})
const appJson = await appResp.json()
if (!appResp.ok) {
throw new Error(appJson.message)
}

5
packages/builder/src/components/start/Steps/Info.svelte

@ -1,6 +1,5 @@
<script>
import { Label, Heading, Input } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
@ -20,8 +19,8 @@
)
return
}
file = fileArray[0]
template.fileImportPath = file.path
file = evt.target.files[0]
template.file = file
}
</script>

2
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",

9
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"
}

10
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,
}
}

285
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"

18
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()

32
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)
}

25
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"
}

18
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 = {

20
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) {

4
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) {

8
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,

21
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
}

6
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)

2
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

5
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))
}
}

4
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()

2
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()

56
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")
})
})
})

2
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

4
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()

2
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()

2
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")

5
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)

2
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()

183
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}`,
})
})
})
})

2
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()

5
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())
}

15
packages/server/src/api/routes/tests/utilities/controllers.js

@ -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"),
}

5
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

53
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()
})
})
})

2
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()

7
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 => {

4
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

33
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}`
)
}
})
}

18
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)
}

8
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: {

7
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

1
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,

19
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,

152
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")
})
})

57
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)
})
})

43
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
})
})

12
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)
})
})

58
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)
})
})

48
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)
})
})

39
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)
})
})

36
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)
})
})

45
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)
})
})

43
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

5
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

1
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 => {

2
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")

7
packages/server/src/selfhost/README.md

@ -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.

44
packages/server/src/selfhost/index.js

@ -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
}

37
packages/server/src/api/routes/tests/utilities/TestConfiguration.js → 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

15
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"),
}

11
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)
}

8
packages/server/src/api/routes/tests/utilities/structures.js → 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 = () => {

2
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"]

16
packages/server/src/utilities/exceptions.js

@ -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)

1
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)

10
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
}

4
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",

4
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"
}

4
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"
}

Loading…
Cancel
Save