Merge branch 'develop' of github.com:Budibase/budibase into public-api-sdk

This commit is contained in:
Andrew Kingston 2022-09-20 11:25:54 +01:00
commit 48d14d9cb3
44 changed files with 1012 additions and 345 deletions

24
.github/ISSUE_TEMPLATE/epic.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Epic
about: Plan a new project
title: ''
labels: epic
assignees: ''
---
## Description
Brief summary of what this Epic is, whether it's a larger project, goal, or user story. Describe the job to be done, which persona this Epic is mainly for, or if more multiple, break it down by user and job story.
## Spec
Link to confluence spec
## Teams and Stakeholders
Describe who needs to be kept up-to-date about this Epic, included in discussions, or updated along the way. Stakeholders can be both in Product/Engineering, as well as other teams like Customer Success who might want to keep customers updated on the Epic project.
## Workflow
- [ ] Spec Created and pasted above
- [ ] Product Review
- [ ] Designs created
- [ ] Individual Tasks created and assigned to Epic

View File

@ -1,5 +1,5 @@
{ {
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -13,6 +13,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kill-port": "^1.6.1", "kill-port": "^1.6.1",
"lerna": "3.14.1", "lerna": "3.14.1",
"madge": "^5.0.1",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"prettier-plugin-svelte": "^2.3.0", "prettier-plugin-svelte": "^2.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@ -25,6 +26,7 @@
"bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh", "bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run build", "build": "lerna run build",
"build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput", "build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro", "release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro",
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop --exact && yarn release:pro:develop", "release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop --exact && yarn release:pro:develop",
"release:pro": "bash scripts/pro/release.sh", "release:pro": "bash scripts/pro/release.sh",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "dependencies": {
"@budibase/types": "1.3.19-alpha.6", "@budibase/types": "1.3.22-alpha.3",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",

View File

@ -44,6 +44,7 @@ export enum DocumentType {
DEV_INFO = "devinfo", DEV_INFO = "devinfo",
AUTOMATION_LOG = "log_au", AUTOMATION_LOG = "log_au",
ACCOUNT_METADATA = "acc_metadata", ACCOUNT_METADATA = "acc_metadata",
PLUGIN = "plg",
} }
export const StaticDatabases = { export const StaticDatabases = {

View File

@ -3,7 +3,7 @@ import { DEFAULT_TENANT_ID, Configs } from "../constants"
import env from "../environment" import env from "../environment"
import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants" import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants"
import { getTenantId, getGlobalDB } from "../context" import { getTenantId, getGlobalDB } from "../context"
import { getGlobalDBName } from "../tenancy/utils" import { getGlobalDBName } from "../tenancy"
import fetch from "node-fetch" import fetch from "node-fetch"
import { doWithDB, allDbs } from "./index" import { doWithDB, allDbs } from "./index"
import { getCouchInfo } from "./pouch" import { getCouchInfo } from "./pouch"
@ -367,6 +367,21 @@ export const generateDevInfoID = (userId: any) => {
return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}` return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
} }
/**
* Generates a new plugin ID - to be used in the global DB.
* @returns {string} The new plugin ID which a plugin metadata document can be stored under.
*/
export const generatePluginID = (name: string) => {
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
}
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
export const getPluginParams = (pluginId?: string | null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}
/** /**
* Returns the most granular configuration document from the DB based on the type, workspace and userID passed. * Returns the most granular configuration document from the DB based on the type, workspace and userID passed.
* @param {Object} db - db instance to query * @param {Object} db - db instance to query

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "1.3.19-alpha.6", "@budibase/string-templates": "1.3.22-alpha.3",
"@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View File

@ -78,7 +78,7 @@
bottom: 0; bottom: 0;
background: var(--background); background: var(--background);
border-top: var(--border-light); border-top: var(--border-light);
z-index: 2; z-index: 3;
} }
.fillWidth { .fillWidth {

View File

@ -48,7 +48,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
top: 15px; top: 15px;
z-index: 100; z-index: 200;
width: 160px; width: 160px;
} }
.icon { .icon {

View File

@ -2,7 +2,7 @@ import filterTests from "../support/filterTests"
const interact = require("../support/interact") const interact = require("../support/interact")
filterTests(["all"], () => { filterTests(["all"], () => {
context("Create Components", () => { xcontext("Create Components", () => {
let headlineId let headlineId
before(() => { before(() => {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -9,6 +9,7 @@
"dev:builder": "routify -c dev:vite", "dev:builder": "routify -c dev:vite",
"dev:vite": "vite --host 0.0.0.0", "dev:vite": "vite --host 0.0.0.0",
"rollup": "rollup -c -w", "rollup": "rollup -c -w",
"test": "jest",
"cy:setup": "ts-node ./cypress/ts/setup.ts", "cy:setup": "ts-node ./cypress/ts/setup.ts",
"cy:setup:ci": "node ./cypress/setup.js", "cy:setup:ci": "node ./cypress/setup.js",
"cy:open": "cypress open", "cy:open": "cypress open",
@ -36,7 +37,8 @@
"components(.*)$": "<rootDir>/src/components$1", "components(.*)$": "<rootDir>/src/components$1",
"builderStore(.*)$": "<rootDir>/src/builderStore$1", "builderStore(.*)$": "<rootDir>/src/builderStore$1",
"stores(.*)$": "<rootDir>/src/stores$1", "stores(.*)$": "<rootDir>/src/stores$1",
"analytics(.*)$": "<rootDir>/src/analytics$1" "analytics(.*)$": "<rootDir>/src/analytics$1",
"constants/backend": "<rootDir>/src/constants/backend/index.js"
}, },
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
@ -69,10 +71,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.19-alpha.6", "@budibase/bbui": "1.3.22-alpha.3",
"@budibase/client": "1.3.19-alpha.6", "@budibase/client": "1.3.22-alpha.3",
"@budibase/frontend-core": "1.3.19-alpha.6", "@budibase/frontend-core": "1.3.22-alpha.3",
"@budibase/string-templates": "1.3.19-alpha.6", "@budibase/string-templates": "1.3.22-alpha.3",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -9,14 +9,14 @@ import {
import { store } from "builderStore" import { store } from "builderStore"
import { import {
queries as queriesStores, queries as queriesStores,
tables as tablesStore,
roles as rolesStore, roles as rolesStore,
tables as tablesStore,
} from "stores/backend" } from "stores/backend"
import { import {
makePropSafe,
isJSBinding,
decodeJSBinding, decodeJSBinding,
encodeJSBinding, encodeJSBinding,
isJSBinding,
makePropSafe,
} from "@budibase/string-templates" } from "@budibase/string-templates"
import { TableNames } from "../constants" import { TableNames } from "../constants"
import { JSONUtils } from "@budibase/frontend-core" import { JSONUtils } from "@budibase/frontend-core"
@ -118,8 +118,7 @@ export const readableToRuntimeMap = (bindings, ctx) => {
return {} return {}
} }
return Object.keys(ctx).reduce((acc, key) => { return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key]) acc[key] = readableToRuntimeBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc return acc
}, {}) }, {})
} }
@ -132,8 +131,7 @@ export const runtimeToReadableMap = (bindings, ctx) => {
return {} return {}
} }
return Object.keys(ctx).reduce((acc, key) => { return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key]) acc[key] = runtimeToReadableBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc return acc
}, {}) }, {})
} }
@ -379,7 +377,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
/** /**
* Gets all bindable properties from the logged in user. * Gets all bindable properties from the logged in user.
*/ */
const getUserBindings = () => { export const getUserBindings = () => {
let bindings = [] let bindings = []
const { schema } = getSchemaForTable(TableNames.USERS) const { schema } = getSchemaForTable(TableNames.USERS)
const keys = Object.keys(schema).sort() const keys = Object.keys(schema).sort()

View File

@ -17,7 +17,7 @@
import ExtraQueryConfig from "./ExtraQueryConfig.svelte" import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte" import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte" import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte" import BindingBuilder from "components/integration/QueryViewerBindingBuilder.svelte"
import { datasources, integrations, queries } from "stores/backend" import { datasources, integrations, queries } from "stores/backend"
import { capitalise } from "../../helpers" import { capitalise } from "../../helpers"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte" import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"

View File

@ -0,0 +1,69 @@
<script>
import { Body, Button, Heading, Layout } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { getUserBindings } from "builderStore/dataBinding"
export let bindable = true
export let queryBindings = []
const userBindings = getUserBindings()
let internalBindings = queryBindings.reduce((acc, binding) => {
acc[binding.name] = binding.default
return acc
}, {})
function newQueryBinding() {
queryBindings = [...queryBindings, {}]
}
</script>
<Layout noPadding={bindable} gap="S">
<div class="controls" class:height={!bindable}>
<Heading size="XS">Bindings</Heading>
{#if !bindable}
<Button secondary on:click={newQueryBinding}>Add Binding</Button>
{/if}
</div>
<Body size="S">
{#if !bindable}
Bindings come in two parts: the binding name, and a default/fallback
value. These bindings can be used as Handlebars expressions throughout the
query.
{:else}
Enter a value for each binding. The default values will be used for any
values left blank.
{/if}
</Body>
<div class="bindings" class:bindable>
<KeyValueBuilder
bind:object={internalBindings}
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
name="binding"
headings
keyPlaceholder="Binding name"
valuePlaceholder="Default"
bindings={[...userBindings]}
bindingDrawerLeft="260px"
on:change={e => {
queryBindings = e.detail.map(binding => {
return {
name: binding.name,
default: binding.value,
}
})
}}
/>
</div>
</Layout>
<style>
.controls {
display: flex;
align-items: center;
justify-content: space-between;
}
.height {
height: 40px;
}
</style>

View File

@ -1,4 +1,5 @@
import { IntegrationTypes } from "constants/backend" import { IntegrationTypes } from "constants/backend"
import { findHBSBlocks } from "@budibase/string-templates"
export function schemaToFields(schema) { export function schemaToFields(schema) {
const response = {} const response = {}
@ -31,7 +32,7 @@ export function breakQueryString(qs) {
let paramObj = {} let paramObj = {}
for (let param of params) { for (let param of params) {
const split = param.split("=") const split = param.split("=")
paramObj[split[0]] = split.slice(1).join("=") paramObj[split[0]] = decodeURIComponent(split.slice(1).join("="))
} }
return paramObj return paramObj
} }
@ -46,7 +47,19 @@ export function buildQueryString(obj) {
if (str !== "") { if (str !== "") {
str += "&" str += "&"
} }
str += `${key}=${encodeURIComponent(value || "")}` const bindings = findHBSBlocks(value)
let count = 0
const bindingMarkers = {}
bindings.forEach(binding => {
const marker = `BINDING...${count++}`
value = value.replace(binding, marker)
bindingMarkers[marker] = binding
})
let encoded = encodeURIComponent(value || "")
Object.entries(bindingMarkers).forEach(([marker, binding]) => {
encoded = encoded.replace(marker, binding)
})
str += `${key}=${encoded}`
} }
} }
return str return str

View File

@ -0,0 +1,37 @@
import { breakQueryString, buildQueryString } from "../data/utils"
describe("check query string utils", () => {
const obj1 = {
key1: "123",
key2: " ",
key3: "333",
}
const obj2 = {
key1: "{{ binding.awd }}",
key2: "{{ binding.sed }} ",
}
it("should build a basic query string", () => {
const queryString = buildQueryString(obj1)
expect(queryString).toBe("key1=123&key2=%20%20%20&key3=333")
})
it("should be able to break a basic query string", () => {
const broken = breakQueryString("key1=123&key2=%20%20%20&key3=333")
expect(broken.key1).toBe(obj1.key1)
expect(broken.key2).toBe(obj1.key2)
expect(broken.key3).toBe(obj1.key3)
})
it("should be able to build with a binding", () => {
const queryString = buildQueryString(obj2)
expect(queryString).toBe("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20")
})
it("should be able to break with a binding", () => {
const broken = breakQueryString("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20")
expect(broken.key1).toBe(obj2.key1)
expect(broken.key2).toBe(obj2.key2)
})
})

View File

@ -708,6 +708,7 @@
.url-block { .url-block {
display: flex; display: flex;
gap: var(--spacing-s); gap: var(--spacing-s);
z-index: 200;
} }
.verb { .verb {
flex: 1; flex: 1;

View File

@ -1,7 +1,7 @@
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { datasources, integrations, tables, views } from "./" import { datasources, integrations, tables, views } from "./"
import { API } from "api" import { API } from "api"
import { duplicateName } from "../../helpers/duplicate" import { duplicateName } from "helpers/duplicate"
const sortQueries = queryList => { const sortQueries = queryList => {
queryList.sort((q1, q2) => { queryList.sort((q1, q2) => {

View File

@ -2,7 +2,7 @@ import { get, writable } from "svelte/store"
import { datasources, queries, views } from "./" import { datasources, queries, views } from "./"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { API } from "api" import { API } from "api"
import { SWITCHABLE_TYPES } from "../../constants/backend" import { SWITCHABLE_TYPES } from "constants/backend"
export function createTablesStore() { export function createTablesStore() {
const store = writable({}) const store = writable({})

View File

@ -1,9 +1,9 @@
import { get } from 'svelte/store' import { get } from "svelte/store"
import api from 'builderStore/api' import { API } from "api"
jest.mock('builderStore/api'); jest.mock("api")
import { SOME_DATASOURCE, SAVE_DATASOURCE} from './fixtures/datasources' import { SOME_DATASOURCE, SAVE_DATASOURCE } from "./fixtures/datasources"
import { createDatasourcesStore } from "../datasources" import { createDatasourcesStore } from "../datasources"
import { queries } from '../queries' import { queries } from '../queries'
@ -12,19 +12,19 @@ describe("Datasources Store", () => {
let store = createDatasourcesStore() let store = createDatasourcesStore()
beforeEach(async () => { beforeEach(async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]}) API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE]})
await store.init() await store.init()
}) })
it("Initialises correctly", async () => { it("Initialises correctly", async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]}) API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE]})
await store.init() await store.init()
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null}) expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null})
}) })
it("fetches all the datasources and updates the store", async () => { it("fetches all the datasources and updates the store", async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE] }) API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE] })
await store.fetch() await store.fetch()
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null }) expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null })
@ -44,7 +44,7 @@ describe("Datasources Store", () => {
}) })
it("saves the datasource, updates the store and returns status message", async () => { it("saves the datasource, updates the store and returns status message", async () => {
api.post.mockReturnValue({ status: 200, json: () => SAVE_DATASOURCE}) API.createDatasource.mockReturnValue({ status: 200, json: () => SAVE_DATASOURCE})
await store.save({ await store.save({
name: 'CoolDB', name: 'CoolDB',
@ -56,11 +56,11 @@ describe("Datasources Store", () => {
expect(get(store).list).toEqual(expect.arrayContaining([SAVE_DATASOURCE.datasource])) expect(get(store).list).toEqual(expect.arrayContaining([SAVE_DATASOURCE.datasource]))
}) })
it("deletes a datasource, updates the store and returns status message", async () => { it("deletes a datasource, updates the store and returns status message", async () => {
api.get.mockReturnValue({ json: () => SOME_DATASOURCE}) API.getDatasources.mockReturnValue({ json: () => SOME_DATASOURCE})
await store.fetch() await store.fetch()
api.delete.mockReturnValue({status: 200, message: 'Datasource deleted.'}) API.deleteDatasource.mockReturnValue({status: 200, message: 'Datasource deleted.'})
await store.delete(SOME_DATASOURCE[0]) await store.delete(SOME_DATASOURCE[0])
expect(get(store)).toEqual({ list: [], selected: null}) expect(get(store)).toEqual({ list: [], selected: null})

View File

@ -1,6 +1,6 @@
import api from 'builderStore/api' import { API } from "api"
jest.mock('builderStore/api'); jest.mock("api")
const PERMISSIONS_FOR_RESOURCE = { const PERMISSIONS_FOR_RESOURCE = {
"write": "BASIC", "write": "BASIC",
@ -13,13 +13,12 @@ describe("Permissions Store", () => {
const store = createPermissionStore() const store = createPermissionStore()
it("fetches permissions for specific resource", async () => { it("fetches permissions for specific resource", async () => {
api.get.mockReturnValueOnce({ json: () => PERMISSIONS_FOR_RESOURCE}) API.getPermissionForResource.mockReturnValueOnce({ json: () => PERMISSIONS_FOR_RESOURCE})
const resourceId = "ta_013657543b4043b89dbb17e9d3a4723a" const resourceId = "ta_013657543b4043b89dbb17e9d3a4723a"
const permissions = await store.forResource(resourceId) const permissions = await store.forResource(resourceId)
expect(api.get).toBeCalledWith(`/api/permission/${resourceId}`)
expect(permissions).toEqual(PERMISSIONS_FOR_RESOURCE) expect(permissions).toEqual(PERMISSIONS_FOR_RESOURCE)
}) })
}) })

View File

@ -1,9 +1,9 @@
import { get } from 'svelte/store' import { get } from "svelte/store"
import api from 'builderStore/api' import { API } from "api"
jest.mock('builderStore/api'); jest.mock("api")
import { SOME_QUERY, SAVE_QUERY_RESPONSE } from './fixtures/queries' import { SOME_QUERY, SAVE_QUERY_RESPONSE } from "./fixtures/queries"
import { createQueriesStore } from "../queries" import { createQueriesStore } from "../queries"
@ -11,26 +11,26 @@ describe("Queries Store", () => {
let store = createQueriesStore() let store = createQueriesStore()
beforeEach(async () => { beforeEach(async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]}) API.getQueries.mockReturnValue({ json: () => [SOME_QUERY]})
await store.init() await store.init()
}) })
it("Initialises correctly", async () => { it("Initialises correctly", async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]}) API.getQueries.mockReturnValue({ json: () => [SOME_QUERY]})
await store.init() await store.init()
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null}) expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
}) })
it("fetches all the queries", async () => { it("fetches all the queries", async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]}) API.getQueries.mockReturnValue({ json: () => [SOME_QUERY]})
await store.fetch() await store.fetch()
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null}) expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
}) })
it("saves the query, updates the store and returns status message", async () => { it("saves the query, updates the store and returns status message", async () => {
api.post.mockReturnValue({ json: () => SAVE_QUERY_RESPONSE}) API.saveQuery.mockReturnValue({ json: () => SAVE_QUERY_RESPONSE})
await store.select(SOME_QUERY.datasourceId, SOME_QUERY) await store.select(SOME_QUERY.datasourceId, SOME_QUERY)
@ -38,7 +38,7 @@ describe("Queries Store", () => {
}) })
it("deletes a query, updates the store and returns status message", async () => { it("deletes a query, updates the store and returns status message", async () => {
api.delete.mockReturnValue({status: 200, message: `Query deleted.`}) API.deleteQuery.mockReturnValue({status: 200, message: `Query deleted.`})
await store.delete(SOME_QUERY) await store.delete(SOME_QUERY)
expect(get(store)).toEqual({ list: [], selected: null}) expect(get(store)).toEqual({ list: [], selected: null})

View File

@ -1,10 +1,10 @@
import { get } from 'svelte/store' import { get } from "svelte/store"
import api from 'builderStore/api' import { API } from "api"
jest.mock('builderStore/api'); jest.mock("api")
import { createRolesStore } from "../roles" import { createRolesStore } from "../roles"
import { ROLES } from './fixtures/roles' import { ROLES } from "./fixtures/roles"
describe("Roles Store", () => { describe("Roles Store", () => {
let store = createRolesStore() let store = createRolesStore()
@ -14,18 +14,17 @@ describe("Roles Store", () => {
}) })
it("fetches roles from backend", async () => { it("fetches roles from backend", async () => {
api.get.mockReturnValue({ json: () => ROLES}) API.getRoles.mockReturnValue({ json: () => ROLES})
await store.fetch() await store.fetch()
expect(api.get).toBeCalledWith("/api/roles")
expect(get(store)).toEqual(ROLES) expect(get(store)).toEqual(ROLES)
}) })
it("deletes a role", async () => { it("deletes a role", async () => {
api.get.mockReturnValueOnce({ json: () => ROLES}) API.getRoles.mockReturnValueOnce({ json: () => ROLES})
await store.fetch() await store.fetch()
api.delete.mockReturnValue({status: 200, message: `Role deleted.`}) API.deleteRole.mockReturnValue({status: 200, message: `Role deleted.`})
const updatedRoles = [...ROLES.slice(1)] const updatedRoles = [...ROLES.slice(1)]
await store.delete(ROLES[0]) await store.delete(ROLES[0])

View File

@ -1,18 +1,16 @@
import { get } from 'svelte/store' import { get } from "svelte/store"
import api from 'builderStore/api' import { API } from "api"
jest.mock('builderStore/api'); jest.mock("api")
import { SOME_TABLES, SAVE_TABLES_RESPONSE, A_TABLE } from './fixtures/tables'
import { SOME_TABLES, SAVE_TABLES_RESPONSE, A_TABLE } from "./fixtures/tables"
import { createTablesStore } from "../tables" import { createTablesStore } from "../tables"
import { views } from '../views'
describe("Tables Store", () => { describe("Tables Store", () => {
let store = createTablesStore() let store = createTablesStore()
beforeEach(async () => { beforeEach(async () => {
api.get.mockReturnValue({ json: () => SOME_TABLES}) API.getTables.mockReturnValue({ json: () => SOME_TABLES})
await store.init() await store.init()
}) })
@ -21,7 +19,7 @@ describe("Tables Store", () => {
}) })
it("fetches all the tables", async () => { it("fetches all the tables", async () => {
api.get.mockReturnValue({ json: () => SOME_TABLES}) API.getTables.mockReturnValue({ json: () => SOME_TABLES})
await store.fetch() await store.fetch()
expect(get(store)).toEqual({ list: SOME_TABLES, selected: {}, draft: {}}) expect(get(store)).toEqual({ list: SOME_TABLES, selected: {}, draft: {}})
@ -42,7 +40,7 @@ describe("Tables Store", () => {
}) })
it("saving a table also selects it", async () => { it("saving a table also selects it", async () => {
api.post.mockReturnValue({ status: 200, json: () => SAVE_TABLES_RESPONSE}) API.post.mockReturnValue({ status: 200, json: () => SAVE_TABLES_RESPONSE})
await store.save(A_TABLE) await store.save(A_TABLE)
@ -50,14 +48,14 @@ describe("Tables Store", () => {
}) })
it("saving the table returns a response", async () => { it("saving the table returns a response", async () => {
api.post.mockReturnValue({ status: 200, json: () => SAVE_TABLES_RESPONSE}) API.saveTable.mockReturnValue({ status: 200, json: () => SAVE_TABLES_RESPONSE})
const response = await store.save(A_TABLE) const response = await store.save(A_TABLE)
expect(response).toEqual(SAVE_TABLES_RESPONSE) expect(response).toEqual(SAVE_TABLES_RESPONSE)
}) })
it("deleting a table removes it from the store", async () => { it("deleting a table removes it from the store", async () => {
api.delete.mockReturnValue({status: 200, message: `Table deleted.`}) API.deleteTable.mockReturnValue({status: 200, message: `Table deleted.`})
await store.delete(A_TABLE) await store.delete(A_TABLE)
expect(get(store).list).toEqual(expect.not.arrayContaining([A_TABLE])) expect(get(store).list).toEqual(expect.not.arrayContaining([A_TABLE]))

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
@ -26,9 +26,7 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "1.3.19-alpha.6", "@budibase/backend-core": "1.3.22-alpha.3",
"@budibase/string-templates": "1.3.19-alpha.6",
"@budibase/types": "1.3.19-alpha.6",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.19-alpha.6", "@budibase/bbui": "1.3.22-alpha.3",
"@budibase/frontend-core": "1.3.19-alpha.6", "@budibase/frontend-core": "1.3.22-alpha.3",
"@budibase/string-templates": "1.3.19-alpha.6", "@budibase/string-templates": "1.3.22-alpha.3",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.19-alpha.6", "@budibase/bbui": "1.3.22-alpha.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -77,11 +77,11 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "1.3.19-alpha.6", "@budibase/backend-core": "1.3.22-alpha.3",
"@budibase/client": "1.3.19-alpha.6", "@budibase/client": "1.3.22-alpha.3",
"@budibase/pro": "1.3.19-alpha.6", "@budibase/pro": "1.3.22-alpha.3",
"@budibase/string-templates": "1.3.19-alpha.6", "@budibase/string-templates": "1.3.22-alpha.3",
"@budibase/types": "1.3.19-alpha.6", "@budibase/types": "1.3.22-alpha.3",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -1,15 +1,15 @@
const { DocumentType, getPluginParams } = require("../../db/utils") import { DocumentType } from "../../db/utils"
const { getComponentLibraryManifest } = require("../../utilities/fileSystem") import { Plugin } from "@budibase/types"
const { getAppDB } = require("@budibase/backend-core/context") import { db as dbCore, context, tenancy } from "@budibase/backend-core"
const { getGlobalDB } = require("@budibase/backend-core/tenancy") import { getComponentLibraryManifest } from "../../utilities/fileSystem"
exports.fetchAppComponentDefinitions = async function (ctx) { exports.fetchAppComponentDefinitions = async function (ctx: any) {
try { try {
const db = getAppDB() const db = context.getAppDB()
const app = await db.get(DocumentType.APP_METADATA) const app = await db.get(DocumentType.APP_METADATA)
let componentManifests = await Promise.all( let componentManifests = await Promise.all(
app.componentLibraries.map(async library => { app.componentLibraries.map(async (library: any) => {
let manifest = await getComponentLibraryManifest(library) let manifest = await getComponentLibraryManifest(library)
return { return {
manifest, manifest,
@ -17,7 +17,7 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
} }
}) })
) )
const definitions = {} const definitions: { [key: string]: any } = {}
for (let { manifest, library } of componentManifests) { for (let { manifest, library } of componentManifests) {
for (let key of Object.keys(manifest)) { for (let key of Object.keys(manifest)) {
if (key === "features") { if (key === "features") {
@ -33,16 +33,16 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
} }
// Add custom components // Add custom components
const globalDB = getGlobalDB() const globalDB = tenancy.getGlobalDB()
const response = await globalDB.allDocs( const response = await globalDB.allDocs(
getPluginParams(null, { dbCore.getPluginParams(null, {
include_docs: true, include_docs: true,
}) })
) )
response.rows response.rows
.map(row => row.doc) .map((row: any) => row.doc)
.filter(plugin => plugin.schema.type === "component") .filter((plugin: Plugin) => plugin.schema.type === "component")
.forEach(plugin => { .forEach((plugin: Plugin) => {
const fullComponentName = `plugin/${plugin.name}` const fullComponentName = `plugin/${plugin.name}`
definitions[fullComponentName] = { definitions[fullComponentName] = {
component: fullComponentName, component: fullComponentName,

View File

@ -1,22 +1,16 @@
import { ObjectStoreBuckets } from "../../../constants"
import { loadJSFile } from "../../../utilities/fileSystem"
import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders" import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders"
import { getGlobalDB } from "@budibase/backend-core/tenancy" import { getGlobalDB } from "@budibase/backend-core/tenancy"
import { validate } from "@budibase/backend-core/plugins" import { validate } from "@budibase/backend-core/plugins"
import { generatePluginID, getPluginParams } from "../../../db/utils" import { PluginType, FileType, PluginSource } from "@budibase/types"
import {
uploadDirectory,
deleteFolder,
} from "@budibase/backend-core/objectStore"
import { PluginType, FileType, PluginSource, Plugin } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { ClientAppSocket } from "../../../websocket" import { ClientAppSocket } from "../../../websocket"
import { events } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { plugins } from "@budibase/pro"
export async function getPlugins(type?: PluginType) { export async function getPlugins(type?: PluginType) {
const db = getGlobalDB() const db = getGlobalDB()
const response = await db.allDocs( const response = await db.allDocs(
getPluginParams(null, { dbCore.getPluginParams(null, {
include_docs: true, include_docs: true,
}) })
) )
@ -37,7 +31,7 @@ export async function upload(ctx: any) {
let docs = [] let docs = []
// can do single or multiple plugins // can do single or multiple plugins
for (let plugin of plugins) { for (let plugin of plugins) {
const doc = await processPlugin(plugin, PluginSource.FILE) const doc = await processUploadedPlugin(plugin, PluginSource.FILE)
docs.push(doc) docs.push(doc)
} }
ctx.body = { ctx.body = {
@ -91,18 +85,19 @@ export async function create(ctx: any) {
) )
} }
const doc = await storePlugin(metadata, directory, source) const doc = await plugins.storePlugin(metadata, directory, source)
ClientAppSocket.emit("plugins-update", { name, hash: doc.hash })
ctx.body = { ctx.body = {
message: "Plugin uploaded successfully", message: "Plugin uploaded successfully",
plugins: [doc], plugins: [doc],
} }
ctx.body = { plugin: doc }
} catch (err: any) { } catch (err: any) {
const errMsg = err?.message ? err?.message : err const errMsg = err?.message ? err?.message : err
ctx.throw(400, `Failed to import plugin: ${errMsg}`) ctx.throw(400, `Failed to import plugin: ${errMsg}`)
} }
ctx.status = 200
} }
export async function fetch(ctx: any) { export async function fetch(ctx: any) {
@ -110,99 +105,21 @@ export async function fetch(ctx: any) {
} }
export async function destroy(ctx: any) { export async function destroy(ctx: any) {
const db = getGlobalDB()
const { pluginId } = ctx.params const { pluginId } = ctx.params
try { try {
const plugin: Plugin = await db.get(pluginId) await plugins.deletePlugin(pluginId)
const bucketPath = `${plugin.name}/`
await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
await db.remove(pluginId, plugin._rev) ctx.body = { message: `Plugin ${ctx.params.pluginId} deleted.` }
await events.plugin.deleted(plugin)
} catch (err: any) { } catch (err: any) {
const errMsg = err?.message ? err?.message : err ctx.throw(400, err.message)
}
ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
} }
ctx.message = `Plugin ${ctx.params.pluginId} deleted.` export async function processUploadedPlugin(
ctx.status = 200 plugin: FileType,
}
export async function storePlugin(
metadata: any,
directory: any,
source?: PluginSource source?: PluginSource
) { ) {
const db = getGlobalDB()
const version = metadata.package.version,
name = metadata.package.name,
description = metadata.package.description,
hash = metadata.schema.hash
// first open the tarball into tmp directory
const bucketPath = `${name}/`
const files = await uploadDirectory(
ObjectStoreBuckets.PLUGINS,
directory,
bucketPath
)
const jsFile = files.find((file: any) => file.name.endsWith(".js"))
if (!jsFile) {
throw new Error(`Plugin missing .js file.`)
}
// validate the JS for a datasource
if (metadata.schema.type === PluginType.DATASOURCE) {
const js = loadJSFile(directory, jsFile.name)
// TODO: this isn't safe - but we need full node environment
// in future we should do this in a thread for safety
try {
eval(js)
} catch (err: any) {
const message = err?.message ? err.message : JSON.stringify(err)
throw new Error(`JS invalid: ${message}`)
}
}
const jsFileName = jsFile.name
const pluginId = generatePluginID(name)
// overwrite existing docs entirely if they exist
let rev
try {
const existing = await db.get(pluginId)
rev = existing._rev
} catch (err) {
rev = undefined
}
let doc: Plugin = {
_id: pluginId,
_rev: rev,
...metadata,
name,
version,
hash,
description,
jsUrl: `${bucketPath}${jsFileName}`,
}
if (source) {
doc = {
...doc,
source,
}
}
const response = await db.put(doc)
await events.plugin.imported(doc)
ClientAppSocket.emit("plugin-update", { name, hash })
return {
...doc,
_rev: response.rev,
}
}
export async function processPlugin(plugin: FileType, source?: PluginSource) {
const { metadata, directory } = await fileUpload(plugin) const { metadata, directory } = await fileUpload(plugin)
validate(metadata?.schema) validate(metadata?.schema)
@ -211,5 +128,7 @@ export async function processPlugin(plugin: FileType, source?: PluginSource) {
throw new Error("Only component plugins are supported outside of self-host") throw new Error("Only component plugins are supported outside of self-host")
} }
return await storePlugin(metadata, directory, source) const doc = await plugins.storePlugin(metadata, directory, source)
ClientAppSocket.emit("plugins-update", { name: doc.name, hash: doc.hash })
return doc
} }

View File

@ -1,17 +1,16 @@
const { import { getScreenParams, generateScreenID, DocumentType } from "../../db/utils"
getScreenParams, import {
generateScreenID, events,
getPluginParams, context,
DocumentType, tenancy,
} = require("../../db/utils") db as dbCore,
const { AccessController } = require("@budibase/backend-core/roles") roles,
const { getAppDB } = require("@budibase/backend-core/context") } from "@budibase/backend-core"
const { events } = require("@budibase/backend-core") import { updateAppPackage } from "./application"
const { getGlobalDB } = require("@budibase/backend-core/tenancy") import { Plugin, ScreenProps } from "@budibase/types"
const { updateAppPackage } = require("./application")
exports.fetch = async ctx => { exports.fetch = async (ctx: any) => {
const db = getAppDB() const db = context.getAppDB()
const screens = ( const screens = (
await db.allDocs( await db.allDocs(
@ -19,16 +18,16 @@ exports.fetch = async ctx => {
include_docs: true, include_docs: true,
}) })
) )
).rows.map(element => element.doc) ).rows.map((el: any) => el.doc)
ctx.body = await new AccessController().checkScreensAccess( ctx.body = await new roles.AccessController().checkScreensAccess(
screens, screens,
ctx.user.role._id ctx.user.role._id
) )
} }
exports.save = async ctx => { exports.save = async (ctx: any) => {
const db = getAppDB() const db = context.getAppDB()
let screen = ctx.request.body let screen = ctx.request.body
let eventFn let eventFn
@ -40,19 +39,19 @@ exports.save = async ctx => {
const response = await db.put(screen) const response = await db.put(screen)
// Find any custom components being used // Find any custom components being used
let pluginNames = [] let pluginNames: string[] = []
let pluginAdded = false let pluginAdded = false
findPlugins(screen.props, pluginNames) findPlugins(screen.props, pluginNames)
if (pluginNames.length) { if (pluginNames.length) {
const globalDB = getGlobalDB() const globalDB = tenancy.getGlobalDB()
const pluginsResponse = await globalDB.allDocs( const pluginsResponse = await globalDB.allDocs(
getPluginParams(null, { dbCore.getPluginParams(null, {
include_docs: true, include_docs: true,
}) })
) )
const requiredPlugins = pluginsResponse.rows const requiredPlugins = pluginsResponse.rows
.map(row => row.doc) .map((row: any) => row.doc)
.filter(plugin => { .filter((plugin: Plugin) => {
return ( return (
plugin.schema.type === "component" && plugin.schema.type === "component" &&
pluginNames.includes(`plugin/${plugin.name}`) pluginNames.includes(`plugin/${plugin.name}`)
@ -63,8 +62,8 @@ exports.save = async ctx => {
const application = await db.get(DocumentType.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)
let usedPlugins = application.usedPlugins || [] let usedPlugins = application.usedPlugins || []
requiredPlugins.forEach(plugin => { requiredPlugins.forEach((plugin: Plugin) => {
if (!usedPlugins.find(x => x._id === plugin._id)) { if (!usedPlugins.find((x: Plugin) => x._id === plugin._id)) {
pluginAdded = true pluginAdded = true
usedPlugins.push({ usedPlugins.push({
_id: plugin._id, _id: plugin._id,
@ -93,8 +92,8 @@ exports.save = async ctx => {
} }
} }
exports.destroy = async ctx => { exports.destroy = async (ctx: any) => {
const db = getAppDB() const db = context.getAppDB()
const id = ctx.params.screenId const id = ctx.params.screenId
const screen = await db.get(id) const screen = await db.get(id)
@ -107,7 +106,7 @@ exports.destroy = async ctx => {
ctx.status = 200 ctx.status = 200
} }
const findPlugins = (component, foundPlugins) => { const findPlugins = (component: ScreenProps, foundPlugins: string[]) => {
if (!component) { if (!component) {
return return
} }

View File

@ -42,7 +42,6 @@ const DocumentType = {
MEM_VIEW: "view", MEM_VIEW: "view",
USER_FLAG: "flag", USER_FLAG: "flag",
AUTOMATION_METADATA: "meta_au", AUTOMATION_METADATA: "meta_au",
PLUGIN: "plg",
} }
const InternalTables = { const InternalTables = {
@ -384,10 +383,3 @@ exports.getMultiIDParams = ids => {
include_docs: true, include_docs: true,
} }
} }
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
exports.getPluginParams = (pluginId = null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}

View File

@ -112,13 +112,6 @@ exports.loadHandlebarsFile = path => {
return fs.readFileSync(path, "utf8") return fs.readFileSync(path, "utf8")
} }
/**
* Same as above just with a different name.
*/
exports.loadJSFile = (directory, name) => {
return fs.readFileSync(join(directory, name), "utf8")
}
/** /**
* When return a file from the API need to write the file to the system temporarily so we * When return a file from the API need to write the file to the system temporarily so we
* can create a read stream to send. * can create a read stream to send.
@ -412,6 +405,7 @@ exports.getDatasourcePlugin = async (name, url, hash) => {
return require(filename) return require(filename)
} else { } else {
console.log(`Updating plugin: ${name}`) console.log(`Updating plugin: ${name}`)
delete require.cache[require.resolve(filename)]
fs.unlinkSync(filename) fs.unlinkSync(filename)
} }
} }

View File

@ -4,7 +4,7 @@ import chokidar from "chokidar"
import fs from "fs" import fs from "fs"
import { tenancy } from "@budibase/backend-core" import { tenancy } from "@budibase/backend-core"
import { DEFAULT_TENANT_ID } from "@budibase/backend-core/constants" import { DEFAULT_TENANT_ID } from "@budibase/backend-core/constants"
import { processPlugin } from "./api/controllers/plugin" import { processUploadedPlugin } from "./api/controllers/plugin"
export function watch() { export function watch() {
const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz") const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz")
@ -28,7 +28,7 @@ export function watch() {
const split = path.split("/") const split = path.split("/")
const name = split[split.length - 1] const name = split[split.length - 1]
console.log("Importing plugin:", path) console.log("Importing plugin:", path)
await processPlugin({ name, path }) await processUploadedPlugin({ name, path })
} catch (err: any) { } catch (err: any) {
const message = err?.message ? err?.message : err const message = err?.message ? err?.message : err
console.error("Failed to import plugin:", message) console.error("Failed to import plugin:", message)

View File

@ -1094,12 +1094,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.19-alpha.6": "@budibase/backend-core@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.19-alpha.6.tgz#12f22db2b09ea7586cafb2eb2c375914c8467188" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.22-alpha.2.tgz#6ae1e42e4163f8ffaa9a7d5891145419f7484279"
integrity sha512-Kp9OaU80fZO1fEi/EA45eSz3zFYWzQHBfuy7XKzJ0MBKYPCvPteIXcXaANeUoHJE9rDGVUHM9nxLxnSfRUGy0Q== integrity sha512-bzBpsq6LmpbwVe6UScgLUcN59pYq0nokzq32VA6rXWfTuPz+Y0jKGJuIY3JkPOuTsVAoqlwQH2XLOs6BZlZ7Ag==
dependencies: dependencies:
"@budibase/types" "1.3.19-alpha.6" "@budibase/types" "1.3.22-alpha.2"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
@ -1180,13 +1180,13 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
"@budibase/pro@1.3.19-alpha.6": "@budibase/pro@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.19-alpha.6.tgz#a30340dbd6aa52658312155a5ce0828d0cfc4621" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.22-alpha.2.tgz#9c0a91d664af9e6cac90d9e4e0bc8cc1d9bdb06c"
integrity sha512-ntyjKIoiAcCIPJAjwmZ19kcHOiduIcuia3htd5njzSNYrPerqmAty8g5oiBrjeK5ckMd8J3VQeCUdk0Lq1Mdfw== integrity sha512-3tVyaXskgMqDFLLPwTa5JgA4OrjIAxrflB8p6jknofQd0UwxHckuInz5BfJVVDqSHi65jif1+YHE0XqSJDuDrg==
dependencies: dependencies:
"@budibase/backend-core" "1.3.19-alpha.6" "@budibase/backend-core" "1.3.22-alpha.2"
"@budibase/types" "1.3.19-alpha.6" "@budibase/types" "1.3.22-alpha.2"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
joi "17.6.0" joi "17.6.0"
node-fetch "^2.6.1" node-fetch "^2.6.1"
@ -1209,10 +1209,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/types@1.3.19-alpha.6": "@budibase/types@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.19-alpha.6.tgz#55ee8c67c2d4bc5ddf009ed1b22094e70d38f8ad" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.22-alpha.2.tgz#4b207cca8f9a897b7d95a074a240fbf8a05b2bd6"
integrity sha512-ApDkaWDpD4BmsmmzxqnSBcj2Q4PGBzkh+49MFZb8F+pdzcDHUQzbVwzVgfBVUS2lHhmZSfBM4sGsfGsBMd+Izw== integrity sha512-zZLhm9q9pZci0R+7EAwYUqkGQ2K+3Dwc5SI9kpQTaWEecLuAmujFERcd6K5wZfWCAzjcb2LsB6lvADxcW6rj3Q==
"@bull-board/api@3.7.0": "@bull-board/api@3.7.0":
version "3.7.0" version "3.7.0"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -1,5 +1,17 @@
import { Document } from "../document" import { Document } from "../document"
export interface ScreenProps extends Document {
_instanceName: string
_styles: { [key: string]: any }
_component: string
_children: ScreenProps[]
size?: string
gap?: string
direction?: string
vAlign?: string
hAlign?: string
}
export interface Screen extends Document { export interface Screen extends Document {
layoutId?: string layoutId?: string
showNavigation?: boolean showNavigation?: boolean
@ -9,4 +21,5 @@ export interface Screen extends Document {
roleId: string roleId: string
homeScreen?: boolean homeScreen?: boolean
} }
props: ScreenProps
} }

View File

@ -23,6 +23,7 @@ export interface Plugin extends Document {
jsUrl?: string jsUrl?: string
source: PluginSource source: PluginSource
package: { [key: string]: any } package: { [key: string]: any }
hash: string
schema: { schema: {
type: PluginType type: PluginType
[key: string]: any [key: string]: any

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.3.19-alpha.6", "version": "1.3.22-alpha.3",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -36,10 +36,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "1.3.19-alpha.6", "@budibase/backend-core": "1.3.22-alpha.3",
"@budibase/pro": "1.3.19-alpha.6", "@budibase/pro": "1.3.22-alpha.3",
"@budibase/string-templates": "1.3.19-alpha.6", "@budibase/string-templates": "1.3.22-alpha.3",
"@budibase/types": "1.3.19-alpha.6", "@budibase/types": "1.3.22-alpha.3",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

View File

@ -95,16 +95,15 @@ const addSessionAttributesToUser = ctx => {
ctx.body.csrfToken = ctx.user.csrfToken ctx.body.csrfToken = ctx.user.csrfToken
} }
/** const sanitiseUserUpdate = ctx => {
* Remove the attributes that are session based from the current user, const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
* so that stale values are not written to the db const resp = {}
*/ for (let [key, value] of Object.entries(ctx.request.body)) {
const removeSessionAttributesFromUser = ctx => { if (allowed.includes(key)) {
delete ctx.request.body.csrfToken resp[key] = value
delete ctx.request.body.account }
delete ctx.request.body.accountPortalAccess }
delete ctx.request.body.budibaseAccess return resp
delete ctx.request.body.license
} }
exports.getSelf = async ctx => { exports.getSelf = async ctx => {
@ -132,10 +131,12 @@ exports.updateSelf = async ctx => {
const db = getGlobalDB() const db = getGlobalDB()
const user = await db.get(ctx.user._id) const user = await db.get(ctx.user._id)
let passwordChange = false let passwordChange = false
if (ctx.request.body.password) {
const userUpdateObj = sanitiseUserUpdate(ctx)
if (userUpdateObj.password) {
// changing password // changing password
passwordChange = true passwordChange = true
ctx.request.body.password = await hash(ctx.request.body.password) userUpdateObj.password = await hash(userUpdateObj.password)
// Log all other sessions out apart from the current one // Log all other sessions out apart from the current one
await platformLogout({ await platformLogout({
ctx, ctx,
@ -143,14 +144,10 @@ exports.updateSelf = async ctx => {
keepActiveSession: true, keepActiveSession: true,
}) })
} }
// don't allow sending up an ID/Rev, always use the existing one
delete ctx.request.body._id
delete ctx.request.body._rev
removeSessionAttributesFromUser(ctx)
const response = await db.put({ const response = await db.put({
...user, ...user,
...ctx.request.body, ...userUpdateObj,
}) })
await userCache.invalidateUser(user._id) await userCache.invalidateUser(user._id)
ctx.body = { ctx.body = {

View File

@ -14,7 +14,6 @@ import {
errors, errors,
events, events,
tenancy, tenancy,
users as usersCore,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { checkAnyUserExists } from "../../../utilities/users" import { checkAnyUserExists } from "../../../utilities/users"
import { groups as groupUtils } from "@budibase/pro" import { groups as groupUtils } from "@budibase/pro"
@ -148,9 +147,7 @@ export const bulkDelete = async (ctx: any) => {
} }
try { try {
let response = await users.bulkDelete(userIds) ctx.body = await users.bulkDelete(userIds)
ctx.body = response
} catch (err) { } catch (err) {
ctx.throw(err) ctx.throw(err)
} }

View File

@ -291,12 +291,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.19-alpha.6": "@budibase/backend-core@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.19-alpha.6.tgz#12f22db2b09ea7586cafb2eb2c375914c8467188" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.22-alpha.2.tgz#6ae1e42e4163f8ffaa9a7d5891145419f7484279"
integrity sha512-Kp9OaU80fZO1fEi/EA45eSz3zFYWzQHBfuy7XKzJ0MBKYPCvPteIXcXaANeUoHJE9rDGVUHM9nxLxnSfRUGy0Q== integrity sha512-bzBpsq6LmpbwVe6UScgLUcN59pYq0nokzq32VA6rXWfTuPz+Y0jKGJuIY3JkPOuTsVAoqlwQH2XLOs6BZlZ7Ag==
dependencies: dependencies:
"@budibase/types" "1.3.19-alpha.6" "@budibase/types" "1.3.22-alpha.2"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
@ -327,21 +327,21 @@
uuid "8.3.2" uuid "8.3.2"
zlib "1.0.5" zlib "1.0.5"
"@budibase/pro@1.3.19-alpha.6": "@budibase/pro@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.19-alpha.6.tgz#a30340dbd6aa52658312155a5ce0828d0cfc4621" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.22-alpha.2.tgz#9c0a91d664af9e6cac90d9e4e0bc8cc1d9bdb06c"
integrity sha512-ntyjKIoiAcCIPJAjwmZ19kcHOiduIcuia3htd5njzSNYrPerqmAty8g5oiBrjeK5ckMd8J3VQeCUdk0Lq1Mdfw== integrity sha512-3tVyaXskgMqDFLLPwTa5JgA4OrjIAxrflB8p6jknofQd0UwxHckuInz5BfJVVDqSHi65jif1+YHE0XqSJDuDrg==
dependencies: dependencies:
"@budibase/backend-core" "1.3.19-alpha.6" "@budibase/backend-core" "1.3.22-alpha.2"
"@budibase/types" "1.3.19-alpha.6" "@budibase/types" "1.3.22-alpha.2"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
joi "17.6.0" joi "17.6.0"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/types@1.3.19-alpha.6": "@budibase/types@1.3.22-alpha.2":
version "1.3.19-alpha.6" version "1.3.22-alpha.2"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.19-alpha.6.tgz#55ee8c67c2d4bc5ddf009ed1b22094e70d38f8ad" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.22-alpha.2.tgz#4b207cca8f9a897b7d95a074a240fbf8a05b2bd6"
integrity sha512-ApDkaWDpD4BmsmmzxqnSBcj2Q4PGBzkh+49MFZb8F+pdzcDHUQzbVwzVgfBVUS2lHhmZSfBM4sGsfGsBMd+Izw== integrity sha512-zZLhm9q9pZci0R+7EAwYUqkGQ2K+3Dwc5SI9kpQTaWEecLuAmujFERcd6K5wZfWCAzjcb2LsB6lvADxcW6rj3Q==
"@cspotcode/source-map-consumer@0.8.0": "@cspotcode/source-map-consumer@0.8.0":
version "0.8.0" version "0.8.0"

645
yarn.lock

File diff suppressed because it is too large Load Diff