diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..e414f48cb8
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,15 @@
+# Security Policy
+
+## Versions
+
+As an open source product, we will only patch the latest major version for security vulnerabilities. Previous versions of budibase will not be retroactively patched.
+
+## Disclosing
+
+You can get in touch with us regarding a vulnerability via email at community@budibase.com.
+
+You can also disclose via huntr.dev. If you believe you have found a vulnerability, please disclose it on huntr and let us know.
+
+https://huntr.dev/bounties/disclose
+
+This will enable us to review the vulnerability and potentially reward you for your work.
diff --git a/hosting/kubernetes/envoy/envoy.yaml b/hosting/kubernetes/envoy/envoy.yaml
index 0e7859d887..1041287f5e 100644
--- a/hosting/kubernetes/envoy/envoy.yaml
+++ b/hosting/kubernetes/envoy/envoy.yaml
@@ -33,11 +33,19 @@ static_resources:
route:
cluster: app-service
- # special case for worker admin API
+ # special cases for worker admin (deprecated), global and system API
+ - match: { prefix: "/api/global/" }
+ route:
+ cluster: worker-service
+
- match: { prefix: "/api/admin/" }
route:
cluster: worker-service
+ - match: { prefix: "/api/system/" }
+ route:
+ cluster: worker-service
+
- match: { path: "/" }
route:
cluster: app-service
diff --git a/lerna.json b/lerna.json
index 644b6a6dca..c4ebd76a28 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/package.json b/package.json
index 05c69e54dc..f8d9d1789e 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna link && lerna bootstrap",
"build": "lerna run build",
- "initialise": "lerna run initialise",
"publishdev": "lerna run publishdev",
"publishnpm": "yarn build && lerna publish --force-publish",
"release": "yarn build && lerna publish patch --yes --force-publish",
diff --git a/packages/auth/package.json b/packages/auth/package.json
index c1f0237343..57895d4dbd 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/auth",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js",
"author": "Budibase",
diff --git a/packages/auth/src/middleware/tenancy.js b/packages/auth/src/middleware/tenancy.js
index b80b9a6763..19cce82273 100644
--- a/packages/auth/src/middleware/tenancy.js
+++ b/packages/auth/src/middleware/tenancy.js
@@ -2,12 +2,13 @@ const { setTenantId } = require("../tenancy")
const ContextFactory = require("../tenancy/FunctionContext")
const { buildMatcherRegex, matches } = require("./matchers")
-module.exports = (allowQueryStringPatterns, noTenancyPatterns) => {
+module.exports = (allowQueryStringPatterns, noTenancyPatterns, opts = {}) => {
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
return ContextFactory.getMiddleware(ctx => {
- const allowNoTenant = !!matches(ctx, noTenancyOptions)
+ const allowNoTenant =
+ opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
const allowQs = !!matches(ctx, allowQsOptions)
setTenantId(ctx, { allowQs, allowNoTenant })
})
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index 6f152c5f1c..27de8f3719 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"license": "AGPL-3.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index b6b61a8d04..11284b8917 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -3,6 +3,7 @@
import "@spectrum-css/table/dist/index-vars.css"
import CellRenderer from "./CellRenderer.svelte"
import SelectEditRenderer from "./SelectEditRenderer.svelte"
+ import { cloneDeep } from "lodash"
/**
* The expected schema is our normal couch schemas for our tables.
@@ -197,7 +198,7 @@
const editRow = (e, row) => {
e.stopPropagation()
- dispatch("editrow", row)
+ dispatch("editrow", cloneDeep(row))
}
const toggleSelectRow = row => {
diff --git a/packages/builder/package.json b/packages/builder/package.json
index 2e8c55c7e2..84553352c9 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@@ -65,10 +65,10 @@
}
},
"dependencies": {
- "@budibase/bbui": "^0.9.121",
- "@budibase/client": "^0.9.121",
+ "@budibase/bbui": "^0.9.123-alpha.1",
+ "@budibase/client": "^0.9.123-alpha.1",
"@budibase/colorpicker": "1.1.2",
- "@budibase/string-templates": "^0.9.121",
+ "@budibase/string-templates": "^0.9.123-alpha.1",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 00eaaf0249..887695d41e 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -1,6 +1,10 @@
import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
-import { findComponent, findComponentPath } from "./storeUtils"
+import {
+ findComponent,
+ findComponentPath,
+ findAllMatchingComponents,
+} from "./storeUtils"
import { store } from "builderStore"
import { tables as tablesStore, queries as queriesStores } from "stores/backend"
import { makePropSafe } from "@budibase/string-templates"
@@ -18,7 +22,9 @@ export const getBindableProperties = (asset, componentId) => {
const userBindings = getUserBindings()
const urlBindings = getUrlBindings(asset)
const deviceBindings = getDeviceBindings()
+ const stateBindings = getStateBindings()
return [
+ ...stateBindings,
...deviceBindings,
...urlBindings,
...contextBindings,
@@ -256,6 +262,18 @@ const getDeviceBindings = () => {
return bindings
}
+/**
+ * Gets all state bindings that are globally available.
+ */
+const getStateBindings = () => {
+ const safeState = makePropSafe("state")
+ return getAllStateVariables().map(key => ({
+ type: "context",
+ runtimeBinding: `${safeState}.${makePropSafe(key)}`,
+ readableBinding: `State.${key}`,
+ }))
+}
+
/**
* Gets all bindable properties from URL parameters.
*/
@@ -458,3 +476,49 @@ export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
"readableBinding"
)
}
+
+/**
+ * Returns an array of the keys of any state variables which are set anywhere
+ * in the app.
+ */
+export const getAllStateVariables = () => {
+ let allComponents = []
+
+ // Find all onClick settings in all layouts
+ get(store).layouts.forEach(layout => {
+ const components = findAllMatchingComponents(
+ layout.props,
+ c => c.onClick != null
+ )
+ allComponents = allComponents.concat(components || [])
+ })
+
+ // Find all onClick settings in all screens
+ get(store).screens.forEach(screen => {
+ const components = findAllMatchingComponents(
+ screen.props,
+ c => c.onClick != null
+ )
+ allComponents = allComponents.concat(components || [])
+ })
+
+ // Add state bindings for all state actions
+ let bindingSet = new Set()
+ allComponents.forEach(component => {
+ if (!Array.isArray(component.onClick)) {
+ return
+ }
+ component.onClick.forEach(action => {
+ if (
+ action["##eventHandlerType"] === "Update State" &&
+ action.parameters?.type === "set" &&
+ action.parameters?.key &&
+ action.parameters?.value
+ ) {
+ bindingSet.add(action.parameters.key)
+ }
+ })
+ })
+
+ return Array.from(bindingSet)
+}
diff --git a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte
index 0b63c0bd4d..740027a8fd 100644
--- a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte
@@ -18,12 +18,10 @@
let exportFormat = FORMATS[0].key
async function exportView() {
- const filename = `export.${exportFormat}`
download(
`/api/views/export?view=${encodeURIComponent(
view
- )}&format=${exportFormat}`,
- filename
+ )}&format=${exportFormat}`
)
}
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte
new file mode 100644
index 0000000000..6c2867d4bf
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ {#if parameters.type === "set"}
+
+
(parameters.value = e.detail)}
+ />
+
+
+
+
+ Persisted values will remain even after reloading the page or closing the
+ browser.
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
index 9cf2461b77..6c285939ac 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
@@ -8,6 +8,7 @@ import LogOut from "./LogOut.svelte"
import ClearForm from "./ClearForm.svelte"
import CloseScreenModal from "./CloseScreenModal.svelte"
import ChangeFormStep from "./ChangeFormStep.svelte"
+import UpdateStateStep from "./UpdateState.svelte"
// Defines which actions are available to configure in the front end.
// Unfortunately the "name" property is used as the identifier so please don't
@@ -57,4 +58,8 @@ export default [
name: "Change Form Step",
component: ChangeFormStep,
},
+ {
+ name: "Update State",
+ component: UpdateStateStep,
+ },
]
diff --git a/packages/cli/package.json b/packages/cli/package.json
index f491ede88e..ec3bcd8d0b 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
diff --git a/packages/client/package.json b/packages/client/package.json
index e26f2a3202..dbd5594be9 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/client",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"license": "MPL-2.0",
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
@@ -18,9 +18,9 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
- "@budibase/bbui": "^0.9.121",
- "@budibase/standard-components": "^0.9.121",
- "@budibase/string-templates": "^0.9.121",
+ "@budibase/bbui": "^0.9.123-alpha.1",
+ "@budibase/standard-components": "^0.9.123-alpha.1",
+ "@budibase/string-templates": "^0.9.123-alpha.1",
"regexparam": "^1.3.0",
"shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5"
diff --git a/packages/client/src/api/rows.js b/packages/client/src/api/rows.js
index 21f8ec1f98..e3557927aa 100644
--- a/packages/client/src/api/rows.js
+++ b/packages/client/src/api/rows.js
@@ -112,16 +112,24 @@ export const enrichRows = async (rows, tableId) => {
if (!Array.isArray(rows)) {
return []
}
- if (rows.length && tableId) {
- // Fetch table schema so we can check column types
- const tableDefinition = await fetchTableDefinition(tableId)
- const schema = tableDefinition && tableDefinition.schema
- if (schema) {
- const keys = Object.keys(schema)
- rows.forEach(row => {
+ if (rows.length) {
+ // map of tables, incase a row being loaded is not from the same table
+ const tables = {}
+ for (let row of rows) {
+ // fallback to passed in tableId if row doesn't have it specified
+ let rowTableId = row.tableId || tableId
+ let table = tables[rowTableId]
+ if (!table) {
+ // Fetch table schema so we can check column types
+ table = await fetchTableDefinition(rowTableId)
+ tables[rowTableId] = table
+ }
+ const schema = table?.schema
+ if (schema) {
+ const keys = Object.keys(schema)
for (let key of keys) {
const type = schema[key].type
- if (type === "link") {
+ if (type === "link" && Array.isArray(row[key])) {
// Enrich row a string join of relationship fields
row[`${key}_text`] =
row[key]
@@ -137,7 +145,7 @@ export const enrichRows = async (rows, tableId) => {
row[`${key}_first`] = url
}
}
- })
+ }
}
}
return rows
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index 11347821a9..d46199dbdd 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -22,6 +22,7 @@
import ErrorSVG from "../../../builder/assets/error.svg"
import UserBindingsProvider from "./UserBindingsProvider.svelte"
import DeviceBindingsProvider from "./DeviceBindingsProvider.svelte"
+ import StateBindingsProvider from "./StateBindingsProvider.svelte"
// Provide contexts
setContext("sdk", SDK)
@@ -85,28 +86,30 @@
{:else if $screenStore.activeLayout}
-
- {#key $screenStore.activeLayout._id}
-
+
+
+ {#key $screenStore.activeLayout._id}
+
+ {/key}
+
+
+
+
+
+ {#key $builderStore.selectedComponentId}
+ {#if $builderStore.inBuilder}
+
+ {/if}
{/key}
-
-
-
-
-
- {#key $builderStore.selectedComponentId}
+
{#if $builderStore.inBuilder}
-
+
+
{/if}
- {/key}
-
- {#if $builderStore.inBuilder}
-
-
- {/if}
+
{/if}
diff --git a/packages/client/src/components/StateBindingsProvider.svelte b/packages/client/src/components/StateBindingsProvider.svelte
new file mode 100644
index 0000000000..b2c049ff31
--- /dev/null
+++ b/packages/client/src/components/StateBindingsProvider.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js
index 43de7bb6da..6c99e3f453 100644
--- a/packages/client/src/store/index.js
+++ b/packages/client/src/store/index.js
@@ -7,6 +7,7 @@ export { builderStore } from "./builder"
export { dataSourceStore } from "./dataSource"
export { confirmationStore } from "./confirmation"
export { peekStore } from "./peek"
+export { stateStore } from "./state"
// Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context"
diff --git a/packages/client/src/store/state.js b/packages/client/src/store/state.js
new file mode 100644
index 0000000000..cb20149de8
--- /dev/null
+++ b/packages/client/src/store/state.js
@@ -0,0 +1,54 @@
+import { writable, get, derived } from "svelte/store"
+import { localStorageStore } from "../../../builder/src/builderStore/store/localStorage"
+import { appStore } from "./app"
+
+const createStateStore = () => {
+ const localStorageKey = `${get(appStore).appId}.state`
+ const persistentStore = localStorageStore(localStorageKey, {})
+
+ // Initialise the temp store to mirror the persistent store
+ const tempStore = writable(get(persistentStore))
+
+ // Sets a value to state, optionally persistent
+ const setValue = (key, value, persist = false) => {
+ const storeToSave = persist ? persistentStore : tempStore
+ const storeToClear = persist ? tempStore : persistentStore
+ storeToSave.update(state => {
+ state[key] = value
+ return state
+ })
+ storeToClear.update(state => {
+ delete state[key]
+ return state
+ })
+ }
+
+ // Delete a certain key from both stores
+ const deleteValue = key => {
+ const stores = [tempStore, persistentStore]
+ stores.forEach(store => {
+ store.update(state => {
+ delete state[key]
+ return state
+ })
+ })
+ }
+
+ // Derive the combination of both persisted and non persisted stores
+ const store = derived(
+ [tempStore, persistentStore],
+ ([$tempStore, $persistentStore]) => {
+ return {
+ ...$tempStore,
+ ...$persistentStore,
+ }
+ }
+ )
+
+ return {
+ subscribe: store.subscribe,
+ actions: { setValue, deleteValue },
+ }
+}
+
+export const stateStore = createStateStore()
diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js
index e6be6ec03d..4413044155 100644
--- a/packages/client/src/utils/buttonActions.js
+++ b/packages/client/src/utils/buttonActions.js
@@ -5,6 +5,7 @@ import {
confirmationStore,
authStore,
peekStore,
+ stateStore,
} from "../store"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
import { ActionTypes } from "../constants"
@@ -122,6 +123,15 @@ const closeScreenModalHandler = () => {
window.dispatchEvent(new Event("close-screen-modal"))
}
+const updateStateHandler = action => {
+ const { type, key, value, persist } = action.parameters
+ if (type === "set") {
+ stateStore.actions.setValue(key, value, persist)
+ } else if (type === "delete") {
+ stateStore.actions.deleteValue(key)
+ }
+}
+
const handlerMap = {
["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler,
@@ -134,6 +144,7 @@ const handlerMap = {
["Clear Form"]: clearFormHandler,
["Close Screen Modal"]: closeScreenModalHandler,
["Change Form Step"]: changeFormStepHandler,
+ ["Update State"]: updateStateHandler,
}
const confirmTextMap = {
diff --git a/packages/server/package.json b/packages/server/package.json
index 39bebd45fa..55bb8072f9 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"description": "Budibase Web Server",
"main": "src/index.js",
"repository": {
@@ -23,7 +23,6 @@
"format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write",
"lint": "eslint --fix src/",
"lint:fix": "yarn run format && yarn run lint",
- "initialise": "node scripts/initialise.js",
"multi:enable": "node scripts/multiTenancy.js enable",
"multi:disable": "node scripts/multiTenancy.js disable"
},
@@ -62,9 +61,9 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@budibase/auth": "^0.9.121",
- "@budibase/client": "^0.9.121",
- "@budibase/string-templates": "^0.9.121",
+ "@budibase/auth": "^0.9.123-alpha.1",
+ "@budibase/client": "^0.9.123-alpha.1",
+ "@budibase/string-templates": "^0.9.123-alpha.1",
"@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1",
@@ -117,7 +116,7 @@
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4",
- "@budibase/standard-components": "^0.9.121",
+ "@budibase/standard-components": "^0.9.123-alpha.1",
"@jest/test-sequencer": "^24.8.0",
"@types/bull": "^3.15.1",
"@types/jest": "^26.0.23",
diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts
index d21b7bc921..eced518604 100644
--- a/packages/server/src/api/controllers/row/ExternalRequest.ts
+++ b/packages/server/src/api/controllers/row/ExternalRequest.ts
@@ -6,8 +6,16 @@ import {
SearchFilters,
SortJson,
} from "../../../definitions/datasource"
-import {Datasource, FieldSchema, Row, Table} from "../../../definitions/common"
-import {breakRowIdField, generateRowIdField} from "../../../integrations/utils"
+import {
+ Datasource,
+ FieldSchema,
+ Row,
+ Table,
+} from "../../../definitions/common"
+import {
+ breakRowIdField,
+ generateRowIdField,
+} from "../../../integrations/utils"
import { RelationshipTypes } from "../../../constants"
interface ManyRelationship {
@@ -348,7 +356,7 @@ module External {
* information.
*/
async lookupRelations(tableId: string, row: Row) {
- const related: {[key: string]: any} = {}
+ const related: { [key: string]: any } = {}
const { tableName } = breakExternalTableId(tableId)
const table = this.tables[tableName]
// @ts-ignore
@@ -387,7 +395,11 @@ module External {
* isn't supposed to exist anymore and delete those. This is better than the usual method of delete them
* all and then re-create, as theres no chance of losing data (e.g. delete succeed, but write fail).
*/
- async handleManyRelationships(mainTableId: string, row: Row, relationships: ManyRelationship[]) {
+ async handleManyRelationships(
+ mainTableId: string,
+ row: Row,
+ relationships: ManyRelationship[]
+ ) {
const { appId } = this
// if we're creating (in a through table) need to wipe the existing ones first
const promises = []
@@ -399,8 +411,10 @@ module External {
// @ts-ignore
const linkPrimary = linkTable.primary[0]
const rows = related[key].rows || []
- const found = rows.find((row: { [key: string]: any }) =>
- row[linkPrimary] === relationship.id || row[linkPrimary] === body[linkPrimary]
+ const found = rows.find(
+ (row: { [key: string]: any }) =>
+ row[linkPrimary] === relationship.id ||
+ row[linkPrimary] === body[linkPrimary]
)
const operation = isUpdate
? DataSourceOperation.UPDATE
@@ -420,13 +434,17 @@ module External {
}
}
// finally cleanup anything that needs to be removed
- for (let [colName, {isMany, rows, tableId}] of Object.entries(related)) {
+ for (let [colName, { isMany, rows, tableId }] of Object.entries(
+ related
+ )) {
const table = this.getTable(tableId)
for (let row of rows) {
const filters = buildFilters(generateIdForRow(row, table), {}, table)
// safety check, if there are no filters on deletion bad things happen
if (Object.keys(filters).length !== 0) {
- const op = isMany ? DataSourceOperation.DELETE : DataSourceOperation.UPDATE
+ const op = isMany
+ ? DataSourceOperation.DELETE
+ : DataSourceOperation.UPDATE
const body = isMany ? null : { [colName]: null }
promises.push(
makeExternalQuery(this.appId, {
@@ -448,7 +466,10 @@ module External {
* Creating the specific list of fields that we desire, and excluding the ones that are no use to us
* is more performant and has the added benefit of protecting against this scenario.
*/
- buildFields(table: Table, includeRelations: IncludeRelationships = IncludeRelationships.INCLUDE) {
+ buildFields(
+ table: Table,
+ includeRelations: IncludeRelationships = IncludeRelationships.INCLUDE
+ ) {
function extractNonLinkFieldNames(table: Table, existing: string[] = []) {
return Object.entries(table.schema)
.filter(
@@ -523,7 +544,10 @@ module External {
// can't really use response right now
const response = await makeExternalQuery(appId, json)
// handle many to many relationships now if we know the ID (could be auto increment)
- if (operation !== DataSourceOperation.READ && processed.manyRelationships) {
+ if (
+ operation !== DataSourceOperation.READ &&
+ processed.manyRelationships
+ ) {
await this.handleManyRelationships(
table._id || "",
response[0],
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index fc86058d08..24567b54a6 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -10,27 +10,6 @@ const env = require("../environment")
const router = new Router()
-const NO_TENANCY_ENDPOINTS = [
- {
- route: "/api/analytics",
- method: "GET",
- },
- {
- route: "/builder",
- method: "GET",
- },
- // when using this locally there can be pass through, need
- // to allow all pass through endpoints to go without tenancy
- {
- route: "/api/global",
- method: "ALL",
- },
- {
- route: "/api/system",
- method: "ALL",
- },
-]
-
router
.use(
compress({
@@ -53,13 +32,21 @@ router
})
.use("/health", ctx => (ctx.status = 200))
.use("/version", ctx => (ctx.body = pkg.version))
+ // re-direct before any middlewares occur
+ .redirect("/", "/builder")
.use(
buildAuthMiddleware(null, {
publicAllowed: true,
})
)
// nothing in the server should allow query string tenants
- .use(buildTenancyMiddleware(null, NO_TENANCY_ENDPOINTS))
+ // the server can be public anywhere, so nowhere should throw errors
+ // if the tenancy has not been set, it'll have to be discovered at application layer
+ .use(
+ buildTenancyMiddleware(null, null, {
+ noTenancyRequired: true,
+ })
+ )
.use(currentApp)
.use(auditLog)
@@ -93,7 +80,4 @@ for (let route of mainRoutes) {
router.use(staticRoutes.routes())
router.use(staticRoutes.allowedMethods())
-// add a redirect for when hitting server directly
-router.redirect("/", "/builder")
-
module.exports = router
diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js
index 840f6454b5..34be44336c 100644
--- a/packages/server/src/db/linkedRows/index.js
+++ b/packages/server/src/db/linkedRows/index.js
@@ -203,19 +203,17 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => {
exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => {
const db = new CouchDB(appId)
// will populate this as we find them
- const linkedTables = []
- for (let [column, schema] of Object.entries(table.schema)) {
- if (schema.type !== FieldTypes.LINK) {
- continue
- }
- for (let row of enriched) {
- if (!row[column] || !row[column].length) {
+ const linkedTables = [table]
+ for (let row of enriched) {
+ // this only fetches the table if its not already in array
+ const rowTable = await getLinkedTable(db, row.tableId, linkedTables)
+ for (let [column, schema] of Object.entries(rowTable.schema)) {
+ if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) {
continue
}
const newLinks = []
for (let link of row[column]) {
const linkTblId = link.tableId || getRelatedTableForField(table, column)
- // this only fetches the table if its not already in array
const linkedTable = await getLinkedTable(db, linkTblId, linkedTables)
const obj = { _id: link._id }
if (link[linkedTable.primaryDisplay]) {
diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts
index a43573ecf7..48fd24e1cf 100644
--- a/packages/server/src/definitions/datasource.ts
+++ b/packages/server/src/definitions/datasource.ts
@@ -42,7 +42,7 @@ export enum SourceNames {
export enum IncludeRelationships {
INCLUDE = 1,
- EXCLUDE = 0
+ EXCLUDE = 0,
}
export interface QueryDefinition {
diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock
index f78f90ea2f..a467846bbb 100644
--- a/packages/server/yarn.lock
+++ b/packages/server/yarn.lock
@@ -943,10 +943,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@budibase/auth@^0.9.121":
- version "0.9.121"
- resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.121.tgz#509ba49ca542fa741424b9a1310cf8e16dc59eeb"
- integrity sha512-SQbp6PsIg0C9IMFMBaFJ8UgJdantheOrCjphMCxpaq3rATl2MOsh9koyo5R/WMYYeBtdG7V+WKd4O5kBUtrfXQ==
+"@budibase/auth@^0.9.123-alpha.1":
+ version "0.9.123-alpha.1"
+ resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.123-alpha.1.tgz#48e011b2afbf0133ebcd564416d0f2399a107b82"
+ integrity sha512-mI0rN0uwCCVjQiwfELjgIm2WpUHfDdzD84lSumRQ8HWAXywD94MdR/qe+B1r7YqjboXxC5SWydoiOivU7jhaTA==
dependencies:
"@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0"
@@ -966,10 +966,10 @@
uuid "^8.3.2"
zlib "^1.0.5"
-"@budibase/bbui@^0.9.121":
- version "0.9.121"
- resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.121.tgz#d374f2856c8e335b0167440dbc2819671203be58"
- integrity sha512-60zNUJ/Nil8yfWdKgjAo4Xxk3mbxipCpZBIgGl/RuE0r6Am0oylRx8pRMmwHaQ92cofbXwApWyC/9VLQQGSJzw==
+"@budibase/bbui@^0.9.123-alpha.1":
+ version "0.9.123-alpha.1"
+ resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.123-alpha.1.tgz#704b693938efdb23eed9d1f089ad86a7f06f35d0"
+ integrity sha512-QGeR3f/xsalkRC3/HRUdG7VaNakYK9MCL3819M5bQxMjyhVoFdZw18mnc7zNfcCL3kTyw0C6capJULho4ic+AA==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
@@ -1015,14 +1015,14 @@
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
-"@budibase/client@^0.9.121":
- version "0.9.121"
- resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.121.tgz#fe00d672eea51bd2c8e60c283332a072168971d1"
- integrity sha512-1IMjIy2IuL5Y3GuIBgNe7ASGloA+As/CUBLtHQMe7oM8Q3FbRBOm7SKyV6FZGYXiG7hggq0fKJqm5YcRO9Lqgw==
+"@budibase/client@^0.9.123-alpha.1":
+ version "0.9.123-alpha.1"
+ resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.123-alpha.1.tgz#a70199937c546544a2750e4273c8347e2fddab53"
+ integrity sha512-s/diiHoN/1kd2JavHy+otYDryfH3ijdjna/jqsZYPK/6Y9ZGcHwtVL43+VVtz9YlBw5aD4Av+YW2t1sqNHCsJw==
dependencies:
- "@budibase/bbui" "^0.9.121"
- "@budibase/standard-components" "^0.9.121"
- "@budibase/string-templates" "^0.9.121"
+ "@budibase/bbui" "^0.9.123-alpha.1"
+ "@budibase/standard-components" "^0.9.123-alpha.1"
+ "@budibase/string-templates" "^0.9.123-alpha.1"
regexparam "^1.3.0"
shortid "^2.2.15"
svelte-spa-router "^3.0.5"
@@ -1055,12 +1055,12 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
-"@budibase/standard-components@^0.9.121":
- version "0.9.121"
- resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.121.tgz#7950a83b1111310b700790a933f68ba9bbababb6"
- integrity sha512-VPbJCisPprPUEVrkFn/yz+ZOTpAJQN+8PRo7gSWtuXLPlLXKdPZ+B8ICDQNA8i3QBswfFACoIZQ7QwEJ+kBJVg==
+"@budibase/standard-components@^0.9.123-alpha.1":
+ version "0.9.123-alpha.1"
+ resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.123-alpha.1.tgz#944e4906e88ce9f8be6a26dc46d50afa17ef3a58"
+ integrity sha512-tCaiOhmmLAOBh+A8rFXnPHKZaSYRK7xlrI23r1IRgvDTkmuflr0o/VrRBVQuaxwWClKQPGkQ3wgnOJgXvk6CgQ==
dependencies:
- "@budibase/bbui" "^0.9.121"
+ "@budibase/bbui" "^0.9.123-alpha.1"
"@spectrum-css/button" "^3.0.3"
"@spectrum-css/card" "^3.0.3"
"@spectrum-css/divider" "^1.0.3"
@@ -1073,10 +1073,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
-"@budibase/string-templates@^0.9.121":
- version "0.9.121"
- resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.121.tgz#bc16761e9571cd44748b414bda24a3830a867672"
- integrity sha512-7j+o9D/qwP1lbclptu5tRLj5AzNRB6RyqAB13DkvfqCDxYkxK5qO3+Gdnm7eVZRguESwCcVEJHe+a/qVBUWhog==
+"@budibase/string-templates@^0.9.123-alpha.1":
+ version "0.9.123-alpha.1"
+ resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.123-alpha.1.tgz#6b2e718478d98aa8ba046ad23e7190894360906a"
+ integrity sha512-Id5AUdzTiYKGo/TTU3cuy1m4hS1Wy66hMIxVCM6GKoEnDinEB/rEA6IwP3IDFyTs2Uld8uWLBnEru93sP982AQ==
dependencies:
"@budibase/handlebars-helpers" "^0.11.4"
dayjs "^1.10.4"
@@ -11353,9 +11353,9 @@ typescript@^4.3.5:
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
uglify-js@^3.1.4:
- version "3.14.1"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06"
- integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g==
+ version "3.14.2"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99"
+ integrity sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==
uid2@0.0.x:
version "0.0.4"
diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json
index 90c4ffa5a0..37cddcdf51 100644
--- a/packages/standard-components/package.json
+++ b/packages/standard-components/package.json
@@ -29,11 +29,11 @@
"keywords": [
"svelte"
],
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"license": "MIT",
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
"dependencies": {
- "@budibase/bbui": "^0.9.121",
+ "@budibase/bbui": "^0.9.123-alpha.1",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",
diff --git a/packages/standard-components/src/forms/Field.svelte b/packages/standard-components/src/forms/Field.svelte
index e490b27dcf..3eb52420e5 100644
--- a/packages/standard-components/src/forms/Field.svelte
+++ b/packages/standard-components/src/forms/Field.svelte
@@ -25,6 +25,7 @@
const labelPosition = fieldGroupContext?.labelPosition || "above"
const formField = formApi?.registerField(
field,
+ type,
defaultValue,
disabled,
validation,
diff --git a/packages/standard-components/src/forms/InnerForm.svelte b/packages/standard-components/src/forms/InnerForm.svelte
index 4c0f6e2b15..75fd4b11d8 100644
--- a/packages/standard-components/src/forms/InnerForm.svelte
+++ b/packages/standard-components/src/forms/InnerForm.svelte
@@ -26,6 +26,7 @@
// Reactive derived stores to derive form state from field array
$: values = deriveFieldProperty(fields, f => f.fieldState.value)
$: errors = deriveFieldProperty(fields, f => f.fieldState.error)
+ $: enrichments = deriveBindingEnrichments(fields)
$: valid = !Object.values($errors).some(error => error != null)
// Derive whether the current form step is valid
@@ -57,6 +58,26 @@
})
}
+ // Derives any enrichments which need to be made so that bindings work for
+ // special data types like attachments. Relationships are currently not
+ // handled as we don't have the primaryDisplay field that is required.
+ const deriveBindingEnrichments = fieldStores => {
+ return derived(fieldStores, fieldValues => {
+ let enrichments = {}
+ fieldValues.forEach(field => {
+ if (field.type === "attachment") {
+ const value = field.fieldState.value
+ let url = null
+ if (Array.isArray(value) && value[0] != null) {
+ url = value[0].url
+ }
+ enrichments[`${field.name}_first`] = url
+ }
+ })
+ return enrichments
+ })
+ }
+
// Searches the field array for a certain field
const getField = name => {
return fields.find(field => get(field).name === name)
@@ -65,6 +86,7 @@
const formApi = {
registerField: (
field,
+ type,
defaultValue = null,
fieldDisabled = false,
validationRules,
@@ -100,6 +122,7 @@
// Construct field info
const fieldInfo = writable({
name: field,
+ type,
step: step || 1,
fieldState: {
fieldId: `id-${generateID()}`,
@@ -262,6 +285,7 @@
$: dataContext = {
...initialValues,
...$values,
+ ...$enrichments,
// These static values are prefixed to avoid clashes with actual columns
__valid: valid,
diff --git a/packages/standard-components/src/lucene.js b/packages/standard-components/src/lucene.js
index 36f6026a06..03baa751cc 100644
--- a/packages/standard-components/src/lucene.js
+++ b/packages/standard-components/src/lucene.js
@@ -105,12 +105,12 @@ export const luceneQuery = (docs, query) => {
// Process an equal match (fails if the value is different)
const equalMatch = match("equal", (key, value, doc) => {
- return doc[key] !== value
+ return value != null && value !== "" && doc[key] !== value
})
// Process a not-equal match (fails if the value is the same)
const notEqualMatch = match("notEqual", (key, value, doc) => {
- return doc[key] === value
+ return value != null && value !== "" && doc[key] === value
})
// Process an empty match (fails if the value is not empty)
diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json
index 664505adf6..0d07dcedac 100644
--- a/packages/string-templates/package.json
+++ b/packages/string-templates/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",
diff --git a/packages/worker/package.json b/packages/worker/package.json
index 4ec96d4854..62edf74006 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
- "version": "0.9.121",
+ "version": "0.9.123-alpha.1",
"description": "Budibase background service",
"main": "src/index.js",
"repository": {
@@ -23,8 +23,8 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@budibase/auth": "^0.9.121",
- "@budibase/string-templates": "^0.9.121",
+ "@budibase/auth": "^0.9.123-alpha.1",
+ "@budibase/string-templates": "^0.9.123-alpha.1",
"@koa/router": "^8.0.0",
"@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.811.0",
diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js
index 7ee933c14a..8abead022e 100644
--- a/packages/worker/src/api/index.js
+++ b/packages/worker/src/api/index.js
@@ -35,6 +35,7 @@ const PUBLIC_ENDPOINTS = [
method: "GET",
},
{
+ // TODO: Add an provisioning API key to this endpoint in the cloud
route: "/api/global/users/init",
method: "POST",
},
@@ -46,6 +47,10 @@ const PUBLIC_ENDPOINTS = [
route: "api/system/flags",
method: "GET",
},
+ {
+ route: "/api/global/users/tenant/:id",
+ method: "GET",
+ },
]
const NO_TENANCY_ENDPOINTS = [
diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js
index 8359835952..a0738acbf5 100644
--- a/packages/worker/src/api/routes/global/users.js
+++ b/packages/worker/src/api/routes/global/users.js
@@ -94,7 +94,7 @@ router
controller.adminUser
)
.get("/api/global/users/self", controller.getSelf)
- .get("/api/global/users/tenant/:id", adminOnly, controller.tenantLookup)
+ .get("/api/global/users/tenant/:id", controller.tenantLookup)
// global endpoint but needs to come at end (blocks other endpoints otherwise)
.get("/api/global/users/:id", adminOnly, controller.find)