Merge branch 'develop' into feature/enterprise
This commit is contained in:
commit
6cd872d7d2
|
@ -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
|
|
@ -59,3 +59,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
install: false
|
install: false
|
||||||
command: yarn test:e2e:ci
|
command: yarn test:e2e:ci
|
||||||
|
|
||||||
|
- name: QA Core Integration Tests
|
||||||
|
run: |
|
||||||
|
cd qa-core
|
||||||
|
yarn
|
||||||
|
yarn api:test:ci
|
|
@ -63,6 +63,7 @@ typings/
|
||||||
|
|
||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
!qa-core/.env
|
||||||
!hosting/.env
|
!hosting/.env
|
||||||
hosting/.generated-nginx.dev.conf
|
hosting/.generated-nginx.dev.conf
|
||||||
hosting/proxy/.generated-nginx.prod.conf
|
hosting/proxy/.generated-nginx.prod.conf
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -45,8 +47,8 @@
|
||||||
"lint:eslint": "eslint packages",
|
"lint:eslint": "eslint packages",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"",
|
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"",
|
||||||
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||||
"lint:fix:eslint": "eslint --fix packages",
|
"lint:fix:eslint": "eslint --fix packages qa-core",
|
||||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"",
|
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
||||||
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||||
"test:e2e": "lerna run cy:test --stream",
|
"test:e2e": "lerna run cy:test --stream",
|
||||||
"test:e2e:ci": "lerna run cy:ci --stream",
|
"test:e2e:ci": "lerna run cy:ci --stream",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/types": "^1.4.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",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import env from "../environment"
|
||||||
import { SEPARATOR, DocumentType } from "../db/constants"
|
import { SEPARATOR, DocumentType } from "../db/constants"
|
||||||
import cls from "./FunctionContext"
|
import cls from "./FunctionContext"
|
||||||
import { dangerousGetDB, closeDB } from "../db"
|
import { dangerousGetDB, closeDB } from "../db"
|
||||||
import { baseGlobalDBName } from "../tenancy/utils"
|
import { baseGlobalDBName } from "../db/tenancy"
|
||||||
import { IdentityContext } from "@budibase/types"
|
import { IdentityContext } from "@budibase/types"
|
||||||
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
||||||
import { ContextKey } from "./constants"
|
import { ContextKey } from "./constants"
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { DEFAULT_TENANT_ID } from "../constants"
|
import { DEFAULT_TENANT_ID } from "../constants"
|
||||||
import { StaticDatabases, SEPARATOR } from "../db/constants"
|
import { StaticDatabases, SEPARATOR } from "./constants"
|
||||||
import { getTenantId } from "../context"
|
import { getTenantId } from "../context"
|
||||||
|
|
||||||
export const getGlobalDBName = (tenantId?: string) => {
|
export const getGlobalDBName = (tenantId?: string) => {
|
|
@ -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"
|
||||||
|
@ -16,6 +16,7 @@ import * as events from "../events"
|
||||||
export * from "./constants"
|
export * from "./constants"
|
||||||
export * from "./conversions"
|
export * from "./conversions"
|
||||||
export { default as Replication } from "./Replication"
|
export { default as Replication } from "./Replication"
|
||||||
|
export * from "./tenancy"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new app ID.
|
* Generates a new app ID.
|
||||||
|
@ -367,6 +368,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
|
||||||
|
|
|
@ -3,12 +3,8 @@ import { doWithDB } from "../db"
|
||||||
import { DocumentType, StaticDatabases } from "../db/constants"
|
import { DocumentType, StaticDatabases } from "../db/constants"
|
||||||
import { getAllApps } from "../db/utils"
|
import { getAllApps } from "../db/utils"
|
||||||
import environment from "../environment"
|
import environment from "../environment"
|
||||||
import {
|
import { doInTenant, getTenantIds, getTenantId } from "../tenancy"
|
||||||
doInTenant,
|
import { getGlobalDBName } from "../db/tenancy"
|
||||||
getTenantIds,
|
|
||||||
getGlobalDBName,
|
|
||||||
getTenantId,
|
|
||||||
} from "../tenancy"
|
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import { DEFINITIONS } from "."
|
import { DEFINITIONS } from "."
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import * as tenancy from "./tenancy"
|
import * as tenancy from "./tenancy"
|
||||||
import * as utils from "./utils"
|
|
||||||
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
...context,
|
...context,
|
||||||
...tenancy,
|
...tenancy,
|
||||||
...utils,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export = pkg
|
export = pkg
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { doWithDB } from "../db"
|
import { doWithDB } from "../db"
|
||||||
import { queryPlatformView } from "../db/views"
|
import { queryPlatformView } from "../db/views"
|
||||||
import { StaticDatabases, ViewName } from "../db/constants"
|
import { StaticDatabases, ViewName } from "../db/constants"
|
||||||
import { getGlobalDBName } from "./utils"
|
import { getGlobalDBName } from "../db/tenancy"
|
||||||
import {
|
import {
|
||||||
getTenantId,
|
getTenantId,
|
||||||
DEFAULT_TENANT_ID,
|
DEFAULT_TENANT_ID,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
||||||
getTenantIDFromAppID,
|
getTenantIDFromAppID,
|
||||||
} from "../context"
|
} from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { PlatformUser, PlatformUserByEmail } from "@budibase/types"
|
import { PlatformUser } from "@budibase/types"
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
|
|
@ -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.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/string-templates": "^1.4.2",
|
||||||
"@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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/bbui": "^1.4.2",
|
||||||
"@budibase/client": "1.3.19-alpha.0",
|
"@budibase/client": "^1.4.2",
|
||||||
"@budibase/frontend-core": "1.3.19-alpha.0",
|
"@budibase/frontend-core": "^1.4.2",
|
||||||
"@budibase/string-templates": "1.3.19-alpha.0",
|
"@budibase/string-templates": "^1.4.2",
|
||||||
"@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",
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
backgroundColour={templateEntry.background}
|
backgroundColour={templateEntry.background}
|
||||||
icon={templateEntry.icon}
|
icon={templateEntry.icon}
|
||||||
>
|
>
|
||||||
{#if $licensing?.usageMetrics?.apps < 100}
|
{#if !($licensing?.usageMetrics?.apps >= 100)}
|
||||||
<Button
|
<Button
|
||||||
cta
|
cta
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
|
@ -8,6 +8,7 @@
|
||||||
import { ExpiringKeys } from "./constants"
|
import { ExpiringKeys } from "./constants"
|
||||||
import { getBanners } from "./licensingBanners"
|
import { getBanners } from "./licensingBanners"
|
||||||
import { banner } from "@budibase/bbui"
|
import { banner } from "@budibase/bbui"
|
||||||
|
import { FEATURE_FLAGS, isEnabled } from "../../../helpers/featureFlags"
|
||||||
|
|
||||||
const oneDayInSeconds = 86400
|
const oneDayInSeconds = 86400
|
||||||
|
|
||||||
|
@ -81,7 +82,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (userLoaded && licensingLoaded && loaded) {
|
$: if (
|
||||||
|
userLoaded &&
|
||||||
|
licensingLoaded &&
|
||||||
|
loaded &&
|
||||||
|
isEnabled(FEATURE_FLAGS.LICENSING)
|
||||||
|
) {
|
||||||
queuedModals = processModals()
|
queuedModals = processModals()
|
||||||
queuedBanners = getBanners()
|
queuedBanners = getBanners()
|
||||||
showNextModal()
|
showNextModal()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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;
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
{
|
{
|
||||||
title: "Plugins",
|
title: "Plugins",
|
||||||
href: "/builder/portal/manage/plugins",
|
href: "/builder/portal/manage/plugins",
|
||||||
badge: "New",
|
badge: "Beta",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
Search,
|
Search,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { plugins } from "stores/portal"
|
import { plugins, admin } from "stores/portal"
|
||||||
import PluginRow from "./_components/PluginRow.svelte"
|
import PluginRow from "./_components/PluginRow.svelte"
|
||||||
import AddPluginModal from "./_components/AddPluginModal.svelte"
|
import AddPluginModal from "./_components/AddPluginModal.svelte"
|
||||||
|
|
||||||
|
@ -20,9 +20,12 @@
|
||||||
let filterOptions = [
|
let filterOptions = [
|
||||||
{ label: "All plugins", value: "all" },
|
{ label: "All plugins", value: "all" },
|
||||||
{ label: "Components", value: "component" },
|
{ label: "Components", value: "component" },
|
||||||
{ label: "Datasources", value: "datasource" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (!$admin.cloud) {
|
||||||
|
filterOptions.push({ label: "Datasources", value: "datasource" })
|
||||||
|
}
|
||||||
|
|
||||||
$: filteredPlugins = $plugins
|
$: filteredPlugins = $plugins
|
||||||
.filter(plugin => {
|
.filter(plugin => {
|
||||||
return filter === "all" || plugin.schema.type === filter
|
return filter === "all" || plugin.schema.type === filter
|
||||||
|
|
|
@ -88,14 +88,14 @@
|
||||||
<Heading size="S">Information</Heading>
|
<Heading size="S">Information</Heading>
|
||||||
<Body size="S">Here you can update your logo and organization name.</Body>
|
<Body size="S">Here you can update your logo and organization name.</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<div className="fields">
|
<div class="fields">
|
||||||
<div className="field">
|
<div class="field">
|
||||||
<Label size="L">Org. name</Label>
|
<Label size="L">Org. name</Label>
|
||||||
<Input thin bind:value={$values.company} />
|
<Input thin bind:value={$values.company} />
|
||||||
</div>
|
</div>
|
||||||
<div className="field logo">
|
<div class="field logo">
|
||||||
<Label size="L">Logo</Label>
|
<Label size="L">Logo</Label>
|
||||||
<div className="file">
|
<div class="file">
|
||||||
<Dropzone
|
<Dropzone
|
||||||
value={[$values.logo]}
|
value={[$values.logo]}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
|
@ -115,13 +115,14 @@
|
||||||
<Heading size="S">Platform</Heading>
|
<Heading size="S">Platform</Heading>
|
||||||
<Body size="S">Here you can set up general platform settings.</Body>
|
<Body size="S">Here you can set up general platform settings.</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<div className="fields">
|
<div class="fields">
|
||||||
<div className="field">
|
<div class="field">
|
||||||
<Label
|
<Label
|
||||||
size="L"
|
size="L"
|
||||||
tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."}
|
tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."}
|
||||||
>Platform URL</Label
|
|
||||||
>
|
>
|
||||||
|
Platform URL
|
||||||
|
</Label>
|
||||||
<Input thin bind:value={$values.platformUrl} />
|
<Input thin bind:value={$values.platformUrl} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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({})
|
||||||
|
|
|
@ -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})
|
|
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -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})
|
|
@ -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])
|
|
@ -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]))
|
|
@ -3,10 +3,12 @@ import { API } from "api"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
import { StripeStatus } from "components/portal/licensing/constants"
|
import { StripeStatus } from "components/portal/licensing/constants"
|
||||||
|
import { FEATURE_FLAGS, isEnabled } from "../../helpers/featureFlags"
|
||||||
|
|
||||||
export const createLicensingStore = () => {
|
export const createLicensingStore = () => {
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
plans: {},
|
plans: {},
|
||||||
|
usageMetrics: {},
|
||||||
}
|
}
|
||||||
const oneDayInMilliseconds = 86400000
|
const oneDayInMilliseconds = 86400000
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ export const createLicensingStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getUsageMetrics: async () => {
|
getUsageMetrics: async () => {
|
||||||
|
if (isEnabled(FEATURE_FLAGS.LICENSING)) {
|
||||||
const quota = get(store).quotaUsage
|
const quota = get(store).quotaUsage
|
||||||
const license = get(auth).user.license
|
const license = get(auth).user.license
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
@ -99,6 +102,7 @@ export const createLicensingStore = () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/backend-core": "^1.4.2",
|
||||||
"@budibase/string-templates": "1.3.19-alpha.0",
|
|
||||||
"@budibase/types": "1.3.19-alpha.0",
|
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/bbui": "^1.4.2",
|
||||||
"@budibase/frontend-core": "1.3.19-alpha.0",
|
"@budibase/frontend-core": "^1.4.2",
|
||||||
"@budibase/string-templates": "1.3.19-alpha.0",
|
"@budibase/string-templates": "^1.4.2",
|
||||||
"@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",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export const PlanType = {
|
export const PlanType = {
|
||||||
FREE: "free",
|
FREE: "free",
|
||||||
|
PRO: "pro",
|
||||||
TEAM: "team",
|
TEAM: "team",
|
||||||
BUSINESS: "business",
|
BUSINESS: "business",
|
||||||
ENTERPRISE: "enterprise",
|
ENTERPRISE: "enterprise",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isFreePlan } from "./utils.js"
|
// import { isFreePlan } from "./utils.js"
|
||||||
|
|
||||||
export const logoEnabled = () => {
|
export const logoEnabled = () => {
|
||||||
return isFreePlan()
|
return false
|
||||||
|
// return isFreePlan()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/bbui": "^1.4.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/backend-core": "^1.4.2",
|
||||||
"@budibase/client": "1.3.19-alpha.0",
|
"@budibase/client": "^1.4.2",
|
||||||
"@budibase/pro": "1.3.19-alpha.0",
|
"@budibase/pro": "1.4.2",
|
||||||
"@budibase/string-templates": "1.3.19-alpha.0",
|
"@budibase/string-templates": "^1.4.2",
|
||||||
"@budibase/types": "1.3.19-alpha.0",
|
"@budibase/types": "^1.4.2",
|
||||||
"@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",
|
||||||
|
|
|
@ -72,6 +72,9 @@ async function up() {
|
||||||
console.log("Spinning up your budibase dev environment... 🔧✨")
|
console.log("Spinning up your budibase dev environment... 🔧✨")
|
||||||
await init()
|
await init()
|
||||||
await compose.upAll(CONFIG)
|
await compose.upAll(CONFIG)
|
||||||
|
|
||||||
|
// We always ensure to restart the proxy service in case of nginx conf changes
|
||||||
|
await compose.restartOne("proxy-service", CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function down() {
|
async function down() {
|
||||||
|
|
|
@ -1659,6 +1659,117 @@
|
||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"rowSearch": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allOr": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used."
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"columnName1": "value",
|
||||||
|
"columnName2": "value"
|
||||||
|
},
|
||||||
|
"description": "A map of field name to the string to search for, this will look for rows that have a value starting with the string value.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The value to search for in the column."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fuzzy": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A fuzzy search, only supported by internal tables."
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches within a range, the format of this must be in the format of an object with a \"low\" and \"high\" property.",
|
||||||
|
"example": {
|
||||||
|
"columnName1": {
|
||||||
|
"low": 10,
|
||||||
|
"high": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equal": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for rows that have a column value that is exactly the value set."
|
||||||
|
},
|
||||||
|
"notEqual": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for any row which does not contain the specified column value."
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.",
|
||||||
|
"example": {
|
||||||
|
"columnName1": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notEmpty": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for rows which have the specified column."
|
||||||
|
},
|
||||||
|
"oneOf": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paginate": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enables pagination, by default this is disabled."
|
||||||
|
},
|
||||||
|
"bookmark": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "If retrieving another page, the bookmark from the previous request must be supplied."
|
||||||
|
},
|
||||||
|
"limit": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000."
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A set of parameters describing the sort behaviour of the search.",
|
||||||
|
"properties": {
|
||||||
|
"order": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ascending",
|
||||||
|
"descending"
|
||||||
|
],
|
||||||
|
"description": "The order of the sort, by default this is ascending."
|
||||||
|
},
|
||||||
|
"column": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the column by which the rows will be sorted."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"string",
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"description": "Defines whether the column should be treated as a string or as numbers when sorting."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"query"
|
||||||
|
]
|
||||||
|
},
|
||||||
"nameSearch": {
|
"nameSearch": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2129,115 +2240,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/rowSearch"
|
||||||
"required": [
|
|
||||||
"query"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"query": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"allOr": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used."
|
|
||||||
},
|
|
||||||
"string": {
|
|
||||||
"type": "object",
|
|
||||||
"example": {
|
|
||||||
"columnName1": "value",
|
|
||||||
"columnName2": "value"
|
|
||||||
},
|
|
||||||
"description": "A map of field name to the string to search for, this will look for rows that have a value starting with the string value.",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The value to search for in the column."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fuzzy": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "A fuzzy search, only supported by internal tables."
|
|
||||||
},
|
|
||||||
"range": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches within a range, the format of this must be in the format of an object with a \"low\" and \"high\" property.",
|
|
||||||
"example": {
|
|
||||||
"columnName1": {
|
|
||||||
"low": 10,
|
|
||||||
"high": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"equal": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches for rows that have a column value that is exactly the value set."
|
|
||||||
},
|
|
||||||
"notEqual": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches for any row which does not contain the specified column value."
|
|
||||||
},
|
|
||||||
"empty": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.",
|
|
||||||
"example": {
|
|
||||||
"columnName1": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notEmpty": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches for rows which have the specified column."
|
|
||||||
},
|
|
||||||
"oneOf": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"paginate": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Enables pagination, by default this is disabled."
|
|
||||||
},
|
|
||||||
"bookmark": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "If retrieving another page, the bookmark from the previous request must be supplied."
|
|
||||||
},
|
|
||||||
"limit": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000."
|
|
||||||
},
|
|
||||||
"sort": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "A set of parameters describing the sort behaviour of the search.",
|
|
||||||
"properties": {
|
|
||||||
"order": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ascending",
|
|
||||||
"descending"
|
|
||||||
],
|
|
||||||
"description": "The order of the sort, by default this is ascending."
|
|
||||||
},
|
|
||||||
"column": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the column by which the rows will be sorted."
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"string",
|
|
||||||
"number"
|
|
||||||
],
|
|
||||||
"description": "Defines whether the column should be treated as a string or as numbers when sorting."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1264,6 +1264,98 @@ components:
|
||||||
- _id
|
- _id
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
|
rowSearch:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
query:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
allOr:
|
||||||
|
type: boolean
|
||||||
|
description: Specifies that a row should be returned if it satisfies any of the
|
||||||
|
specified options, rather than requiring it to fulfill all the
|
||||||
|
search parameters. This defaults to false, meaning AND logic
|
||||||
|
will be used.
|
||||||
|
string:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
columnName1: value
|
||||||
|
columnName2: value
|
||||||
|
description: A map of field name to the string to search for, this will look for
|
||||||
|
rows that have a value starting with the string value.
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: The value to search for in the column.
|
||||||
|
fuzzy:
|
||||||
|
type: object
|
||||||
|
description: A fuzzy search, only supported by internal tables.
|
||||||
|
range:
|
||||||
|
type: object
|
||||||
|
description: Searches within a range, the format of this must be in the format
|
||||||
|
of an object with a "low" and "high" property.
|
||||||
|
example:
|
||||||
|
columnName1:
|
||||||
|
low: 10
|
||||||
|
high: 20
|
||||||
|
equal:
|
||||||
|
type: object
|
||||||
|
description: Searches for rows that have a column value that is exactly the
|
||||||
|
value set.
|
||||||
|
notEqual:
|
||||||
|
type: object
|
||||||
|
description: Searches for any row which does not contain the specified column
|
||||||
|
value.
|
||||||
|
empty:
|
||||||
|
type: object
|
||||||
|
description: Searches for rows which do not contain the specified column. The
|
||||||
|
object should simply contain keys of the column names, these can
|
||||||
|
map to any value.
|
||||||
|
example:
|
||||||
|
columnName1: ""
|
||||||
|
notEmpty:
|
||||||
|
type: object
|
||||||
|
description: Searches for rows which have the specified column.
|
||||||
|
oneOf:
|
||||||
|
type: object
|
||||||
|
description: Searches for rows which have a column value that is any of the
|
||||||
|
specified values. The format of this must be columnName ->
|
||||||
|
[value1, value2].
|
||||||
|
paginate:
|
||||||
|
type: boolean
|
||||||
|
description: Enables pagination, by default this is disabled.
|
||||||
|
bookmark:
|
||||||
|
oneOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
|
description: If retrieving another page, the bookmark from the previous request
|
||||||
|
must be supplied.
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
description: The maximum number of rows to return, useful when paginating, for
|
||||||
|
internal tables this will be limited to 1000, for SQL tables it will
|
||||||
|
be 5000.
|
||||||
|
sort:
|
||||||
|
type: object
|
||||||
|
description: A set of parameters describing the sort behaviour of the search.
|
||||||
|
properties:
|
||||||
|
order:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- ascending
|
||||||
|
- descending
|
||||||
|
description: The order of the sort, by default this is ascending.
|
||||||
|
column:
|
||||||
|
type: string
|
||||||
|
description: The name of the column by which the rows will be sorted.
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- string
|
||||||
|
- number
|
||||||
|
description: Defines whether the column should be treated as a string or as
|
||||||
|
numbers when sorting.
|
||||||
|
required:
|
||||||
|
- query
|
||||||
nameSearch:
|
nameSearch:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1544,97 +1636,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
$ref: "#/components/schemas/rowSearch"
|
||||||
required:
|
|
||||||
- query
|
|
||||||
properties:
|
|
||||||
query:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
allOr:
|
|
||||||
type: boolean
|
|
||||||
description: Specifies that a row should be returned if it satisfies any of the
|
|
||||||
specified options, rather than requiring it to fulfill
|
|
||||||
all the search parameters. This defaults to false,
|
|
||||||
meaning AND logic will be used.
|
|
||||||
string:
|
|
||||||
type: object
|
|
||||||
example:
|
|
||||||
columnName1: value
|
|
||||||
columnName2: value
|
|
||||||
description: A map of field name to the string to search for, this will look for
|
|
||||||
rows that have a value starting with the string value.
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
description: The value to search for in the column.
|
|
||||||
fuzzy:
|
|
||||||
type: object
|
|
||||||
description: A fuzzy search, only supported by internal tables.
|
|
||||||
range:
|
|
||||||
type: object
|
|
||||||
description: Searches within a range, the format of this must be in the format
|
|
||||||
of an object with a "low" and "high" property.
|
|
||||||
example:
|
|
||||||
columnName1:
|
|
||||||
low: 10
|
|
||||||
high: 20
|
|
||||||
equal:
|
|
||||||
type: object
|
|
||||||
description: Searches for rows that have a column value that is exactly the
|
|
||||||
value set.
|
|
||||||
notEqual:
|
|
||||||
type: object
|
|
||||||
description: Searches for any row which does not contain the specified column
|
|
||||||
value.
|
|
||||||
empty:
|
|
||||||
type: object
|
|
||||||
description: Searches for rows which do not contain the specified column. The
|
|
||||||
object should simply contain keys of the column names,
|
|
||||||
these can map to any value.
|
|
||||||
example:
|
|
||||||
columnName1: ""
|
|
||||||
notEmpty:
|
|
||||||
type: object
|
|
||||||
description: Searches for rows which have the specified column.
|
|
||||||
oneOf:
|
|
||||||
type: object
|
|
||||||
description: Searches for rows which have a column value that is any of the
|
|
||||||
specified values. The format of this must be columnName
|
|
||||||
-> [value1, value2].
|
|
||||||
paginate:
|
|
||||||
type: boolean
|
|
||||||
description: Enables pagination, by default this is disabled.
|
|
||||||
bookmark:
|
|
||||||
oneOf:
|
|
||||||
- type: string
|
|
||||||
- type: integer
|
|
||||||
description: If retrieving another page, the bookmark from the previous request
|
|
||||||
must be supplied.
|
|
||||||
limit:
|
|
||||||
type: integer
|
|
||||||
description: The maximum number of rows to return, useful when paginating, for
|
|
||||||
internal tables this will be limited to 1000, for SQL tables
|
|
||||||
it will be 5000.
|
|
||||||
sort:
|
|
||||||
type: object
|
|
||||||
description: A set of parameters describing the sort behaviour of the search.
|
|
||||||
properties:
|
|
||||||
order:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- ascending
|
|
||||||
- descending
|
|
||||||
description: The order of the sort, by default this is ascending.
|
|
||||||
column:
|
|
||||||
type: string
|
|
||||||
description: The name of the column by which the rows will be sorted.
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- string
|
|
||||||
- number
|
|
||||||
description: Defines whether the column should be treated as a string or as
|
|
||||||
numbers when sorting.
|
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The response will contain an array of rows that match the search
|
description: The response will contain an array of rows that match the search
|
||||||
|
|
|
@ -2,6 +2,122 @@ const { object } = require("./utils")
|
||||||
const Resource = require("./utils/Resource")
|
const Resource = require("./utils/Resource")
|
||||||
|
|
||||||
module.exports = new Resource().setSchemas({
|
module.exports = new Resource().setSchemas({
|
||||||
|
rowSearch: object(
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
allOr: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used.",
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
type: "object",
|
||||||
|
example: {
|
||||||
|
columnName1: "value",
|
||||||
|
columnName2: "value",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"A map of field name to the string to search for, this will look for rows that have a value starting with the string value.",
|
||||||
|
additionalProperties: {
|
||||||
|
type: "string",
|
||||||
|
description: "The value to search for in the column.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fuzzy: {
|
||||||
|
type: "object",
|
||||||
|
description: "A fuzzy search, only supported by internal tables.",
|
||||||
|
},
|
||||||
|
range: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
'Searches within a range, the format of this must be in the format of an object with a "low" and "high" property.',
|
||||||
|
example: {
|
||||||
|
columnName1: {
|
||||||
|
low: 10,
|
||||||
|
high: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
equal: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"Searches for rows that have a column value that is exactly the value set.",
|
||||||
|
},
|
||||||
|
notEqual: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"Searches for any row which does not contain the specified column value.",
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.",
|
||||||
|
example: {
|
||||||
|
columnName1: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notEmpty: {
|
||||||
|
type: "object",
|
||||||
|
description: "Searches for rows which have the specified column.",
|
||||||
|
},
|
||||||
|
oneOf: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2].",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paginate: {
|
||||||
|
type: "boolean",
|
||||||
|
description: "Enables pagination, by default this is disabled.",
|
||||||
|
},
|
||||||
|
bookmark: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
"If retrieving another page, the bookmark from the previous request must be supplied.",
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: "integer",
|
||||||
|
description:
|
||||||
|
"The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000.",
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"A set of parameters describing the sort behaviour of the search.",
|
||||||
|
properties: {
|
||||||
|
order: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["ascending", "descending"],
|
||||||
|
description: "The order of the sort, by default this is ascending.",
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"The name of the column by which the rows will be sorted.",
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["string", "number"],
|
||||||
|
description:
|
||||||
|
"Defines whether the column should be treated as a string or as numbers when sorting.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: ["query"],
|
||||||
|
}
|
||||||
|
),
|
||||||
nameSearch: object({
|
nameSearch: object({
|
||||||
name: {
|
name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { getAllApps } = require("@budibase/backend-core/db")
|
const { getAllApps, getGlobalDBName } = require("@budibase/backend-core/db")
|
||||||
const {
|
const {
|
||||||
exportDB,
|
exportDB,
|
||||||
sendTempFile,
|
sendTempFile,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
} = require("../../utilities/fileSystem")
|
} = require("../../utilities/fileSystem")
|
||||||
const { stringToReadStream } = require("../../utilities")
|
const { stringToReadStream } = require("../../utilities")
|
||||||
const {
|
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||||
getGlobalDBName,
|
|
||||||
getGlobalDB,
|
|
||||||
} = require("@budibase/backend-core/tenancy")
|
|
||||||
const { create } = require("./application")
|
const { create } = require("./application")
|
||||||
const { getDocParams, DocumentType, isDevAppID } = require("../../db/utils")
|
const { getDocParams, DocumentType, isDevAppID } = require("../../db/utils")
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,18 @@ export type Query = components["schemas"]["query"]
|
||||||
export type ExecuteQuery = components["schemas"]["executeQueryOutput"]
|
export type ExecuteQuery = components["schemas"]["executeQueryOutput"]
|
||||||
|
|
||||||
export type Application = components["schemas"]["applicationOutput"]["data"]
|
export type Application = components["schemas"]["applicationOutput"]["data"]
|
||||||
|
export type CreateApplicationParams = components["schemas"]["application"]
|
||||||
|
|
||||||
export type Table = components["schemas"]["tableOutput"]["data"]
|
export type Table = components["schemas"]["tableOutput"]["data"]
|
||||||
|
export type CreateTableParams = components["schemas"]["table"]
|
||||||
|
|
||||||
export type Row = components["schemas"]["rowOutput"]["data"]
|
export type Row = components["schemas"]["rowOutput"]["data"]
|
||||||
export type RowSearch = components["schemas"]["searchOutput"]
|
export type RowSearch = components["schemas"]["searchOutput"]
|
||||||
|
export type CreateRowParams = components["schemas"]["row"]
|
||||||
|
|
||||||
export type User = components["schemas"]["userOutput"]["data"]
|
export type User = components["schemas"]["userOutput"]["data"]
|
||||||
|
export type CreateUserParams = components["schemas"]["user"]
|
||||||
|
|
||||||
|
export type SearchInputParams =
|
||||||
|
| components["schemas"]["nameSearch"]
|
||||||
|
| components["schemas"]["rowSearch"]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -143,88 +143,7 @@ read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read))
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* $ref: '#/components/schemas/rowSearch'
|
||||||
* required:
|
|
||||||
* - query
|
|
||||||
* properties:
|
|
||||||
* query:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* allOr:
|
|
||||||
* type: boolean
|
|
||||||
* description: Specifies that a row should be returned if it satisfies
|
|
||||||
* any of the specified options, rather than requiring it to fulfill all
|
|
||||||
* the search parameters. This defaults to false, meaning AND logic will be used.
|
|
||||||
* string:
|
|
||||||
* type: object
|
|
||||||
* example:
|
|
||||||
* columnName1: value
|
|
||||||
* columnName2: value
|
|
||||||
* description: A map of field name to the string to search for,
|
|
||||||
* this will look for rows that have a value starting with the
|
|
||||||
* string value.
|
|
||||||
* additionalProperties:
|
|
||||||
* type: string
|
|
||||||
* description: The value to search for in the column.
|
|
||||||
* fuzzy:
|
|
||||||
* type: object
|
|
||||||
* description: A fuzzy search, only supported by internal tables.
|
|
||||||
* range:
|
|
||||||
* type: object
|
|
||||||
* description: Searches within a range, the format of this must be
|
|
||||||
* in the format of an object with a "low" and "high" property.
|
|
||||||
* example:
|
|
||||||
* columnName1: { low: 10, high: 20 }
|
|
||||||
* equal:
|
|
||||||
* type: object
|
|
||||||
* description: Searches for rows that have a column value that is
|
|
||||||
* exactly the value set.
|
|
||||||
* notEqual:
|
|
||||||
* type: object
|
|
||||||
* description: Searches for any row which does not contain the specified
|
|
||||||
* column value.
|
|
||||||
* empty:
|
|
||||||
* type: object
|
|
||||||
* description: Searches for rows which do not contain the specified column.
|
|
||||||
* The object should simply contain keys of the column names, these
|
|
||||||
* can map to any value.
|
|
||||||
* example:
|
|
||||||
* columnName1: ""
|
|
||||||
* notEmpty:
|
|
||||||
* type: object
|
|
||||||
* description: Searches for rows which have the specified column.
|
|
||||||
* oneOf:
|
|
||||||
* type: object
|
|
||||||
* description: Searches for rows which have a column value that is any
|
|
||||||
* of the specified values. The format of this must be columnName -> [value1, value2].
|
|
||||||
* paginate:
|
|
||||||
* type: boolean
|
|
||||||
* description: Enables pagination, by default this is disabled.
|
|
||||||
* bookmark:
|
|
||||||
* oneOf:
|
|
||||||
* - type: string
|
|
||||||
* - type: integer
|
|
||||||
* description: If retrieving another page, the bookmark from the previous request must be supplied.
|
|
||||||
* limit:
|
|
||||||
* type: integer
|
|
||||||
* description: The maximum number of rows to return, useful when paginating, for internal tables this
|
|
||||||
* will be limited to 1000, for SQL tables it will be 5000.
|
|
||||||
* sort:
|
|
||||||
* type: object
|
|
||||||
* description: A set of parameters describing the sort behaviour of the search.
|
|
||||||
* properties:
|
|
||||||
* order:
|
|
||||||
* type: string
|
|
||||||
* enum: [ascending, descending]
|
|
||||||
* description: The order of the sort, by default this is ascending.
|
|
||||||
* column:
|
|
||||||
* type: string
|
|
||||||
* description: The name of the column by which the rows will be sorted.
|
|
||||||
* type:
|
|
||||||
* type: string
|
|
||||||
* enum: [string, number]
|
|
||||||
* description: Defines whether the column should be treated as a string
|
|
||||||
* or as numbers when sorting.
|
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: The response will contain an array of rows that match the search parameters.
|
* description: The response will contain an array of rows that match the search parameters.
|
||||||
|
|
|
@ -30,7 +30,11 @@ const fs = require("fs")
|
||||||
import redis from "./utilities/redis"
|
import redis from "./utilities/redis"
|
||||||
import * as migrations from "./migrations"
|
import * as migrations from "./migrations"
|
||||||
import { events, installation, tenancy } from "@budibase/backend-core"
|
import { events, installation, tenancy } from "@budibase/backend-core"
|
||||||
import { createAdminUser, getChecklist } from "./utilities/workerRequests"
|
import {
|
||||||
|
createAdminUser,
|
||||||
|
generateApiKey,
|
||||||
|
getChecklist,
|
||||||
|
} from "./utilities/workerRequests"
|
||||||
import { watch } from "./watch"
|
import { watch } from "./watch"
|
||||||
import { initialise as initialiseWebsockets } from "./websocket"
|
import { initialise as initialiseWebsockets } from "./websocket"
|
||||||
|
|
||||||
|
@ -127,11 +131,16 @@ module.exports = server.listen(env.PORT || 0, async () => {
|
||||||
if (!checklist?.adminUser?.checked) {
|
if (!checklist?.adminUser?.checked) {
|
||||||
try {
|
try {
|
||||||
const tenantId = tenancy.getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
await createAdminUser(
|
const user = await createAdminUser(
|
||||||
env.BB_ADMIN_USER_EMAIL,
|
env.BB_ADMIN_USER_EMAIL,
|
||||||
env.BB_ADMIN_USER_PASSWORD,
|
env.BB_ADMIN_USER_PASSWORD,
|
||||||
tenantId
|
tenantId
|
||||||
)
|
)
|
||||||
|
// Need to set up an API key for automated integration tests
|
||||||
|
if (env.isTest()) {
|
||||||
|
await generateApiKey(user._id)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"Admin account automatically created for",
|
"Admin account automatically created for",
|
||||||
env.BB_ADMIN_USER_EMAIL
|
env.BB_ADMIN_USER_EMAIL
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -278,58 +278,7 @@ export interface paths {
|
||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": components["schemas"]["rowSearch"];
|
||||||
query: {
|
|
||||||
/** @description Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used. */
|
|
||||||
allOr?: boolean;
|
|
||||||
/**
|
|
||||||
* @description A map of field name to the string to search for, this will look for rows that have a value starting with the string value.
|
|
||||||
* @example [object Object]
|
|
||||||
*/
|
|
||||||
string?: { [key: string]: string };
|
|
||||||
/** @description A fuzzy search, only supported by internal tables. */
|
|
||||||
fuzzy?: { [key: string]: unknown };
|
|
||||||
/**
|
|
||||||
* @description Searches within a range, the format of this must be in the format of an object with a "low" and "high" property.
|
|
||||||
* @example [object Object]
|
|
||||||
*/
|
|
||||||
range?: { [key: string]: unknown };
|
|
||||||
/** @description Searches for rows that have a column value that is exactly the value set. */
|
|
||||||
equal?: { [key: string]: unknown };
|
|
||||||
/** @description Searches for any row which does not contain the specified column value. */
|
|
||||||
notEqual?: { [key: string]: unknown };
|
|
||||||
/**
|
|
||||||
* @description Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.
|
|
||||||
* @example [object Object]
|
|
||||||
*/
|
|
||||||
empty?: { [key: string]: unknown };
|
|
||||||
/** @description Searches for rows which have the specified column. */
|
|
||||||
notEmpty?: { [key: string]: unknown };
|
|
||||||
/** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */
|
|
||||||
oneOf?: { [key: string]: unknown };
|
|
||||||
};
|
|
||||||
/** @description Enables pagination, by default this is disabled. */
|
|
||||||
paginate?: boolean;
|
|
||||||
/** @description If retrieving another page, the bookmark from the previous request must be supplied. */
|
|
||||||
bookmark?: string | number;
|
|
||||||
/** @description The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000. */
|
|
||||||
limit?: number;
|
|
||||||
/** @description A set of parameters describing the sort behaviour of the search. */
|
|
||||||
sort?: {
|
|
||||||
/**
|
|
||||||
* @description The order of the sort, by default this is ascending.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
order?: "ascending" | "descending";
|
|
||||||
/** @description The name of the column by which the rows will be sorted. */
|
|
||||||
column?: string;
|
|
||||||
/**
|
|
||||||
* @description Defines whether the column should be treated as a string or as numbers when sorting.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
type?: "string" | "number";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1105,6 +1054,58 @@ export interface components {
|
||||||
_id: string;
|
_id: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
rowSearch: {
|
||||||
|
query: {
|
||||||
|
/** @description Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used. */
|
||||||
|
allOr?: boolean;
|
||||||
|
/**
|
||||||
|
* @description A map of field name to the string to search for, this will look for rows that have a value starting with the string value.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
string?: { [key: string]: string };
|
||||||
|
/** @description A fuzzy search, only supported by internal tables. */
|
||||||
|
fuzzy?: { [key: string]: unknown };
|
||||||
|
/**
|
||||||
|
* @description Searches within a range, the format of this must be in the format of an object with a "low" and "high" property.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
range?: { [key: string]: unknown };
|
||||||
|
/** @description Searches for rows that have a column value that is exactly the value set. */
|
||||||
|
equal?: { [key: string]: unknown };
|
||||||
|
/** @description Searches for any row which does not contain the specified column value. */
|
||||||
|
notEqual?: { [key: string]: unknown };
|
||||||
|
/**
|
||||||
|
* @description Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
empty?: { [key: string]: unknown };
|
||||||
|
/** @description Searches for rows which have the specified column. */
|
||||||
|
notEmpty?: { [key: string]: unknown };
|
||||||
|
/** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */
|
||||||
|
oneOf?: { [key: string]: unknown };
|
||||||
|
};
|
||||||
|
/** @description Enables pagination, by default this is disabled. */
|
||||||
|
paginate?: boolean;
|
||||||
|
/** @description If retrieving another page, the bookmark from the previous request must be supplied. */
|
||||||
|
bookmark?: string | number;
|
||||||
|
/** @description The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000. */
|
||||||
|
limit?: number;
|
||||||
|
/** @description A set of parameters describing the sort behaviour of the search. */
|
||||||
|
sort?: {
|
||||||
|
/**
|
||||||
|
* @description The order of the sort, by default this is ascending.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
order?: "ascending" | "descending";
|
||||||
|
/** @description The name of the column by which the rows will be sorted. */
|
||||||
|
column?: string;
|
||||||
|
/**
|
||||||
|
* @description Defines whether the column should be treated as a string or as numbers when sorting.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
type?: "string" | "number";
|
||||||
|
};
|
||||||
|
};
|
||||||
nameSearch: {
|
nameSearch: {
|
||||||
/** @description The name to be used when searching - this will be used in a case insensitive starts with match. */
|
/** @description The name to be used when searching - this will be used in a case insensitive starts with match. */
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,3 +153,11 @@ exports.getChecklist = async () => {
|
||||||
)
|
)
|
||||||
return checkResponse(response, "get checklist")
|
return checkResponse(response, "get checklist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.generateApiKey = async userId => {
|
||||||
|
const response = await fetch(
|
||||||
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/self/api_key"),
|
||||||
|
request(null, { method: "POST", body: { userId } })
|
||||||
|
)
|
||||||
|
return checkResponse(response, "generate API key")
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.0":
|
"@budibase/backend-core@1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.19-alpha.0.tgz#e9785994485b16eb0c2f082fe5803c690199a397"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.2.tgz#9c0a40f5c4a1693fd24048f6f130547b0f08e76e"
|
||||||
integrity sha512-avYT4DLDG3GMDNOKHgbNRJIqzHimdhSxjiifbVnMfizLjWL94eAqVgRA4kOiWkEuuH6Wlc5dmI359dJvsWKErw==
|
integrity sha512-htXj7V5mLdf+IqATBlgjABknbT6n/5qRwArlsawYYZzDRTod3rK+W7dIGHDr0JOPJJ8272SuAcJbF80gd65Z+w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "1.3.19-alpha.0"
|
"@budibase/types" "^1.4.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.0":
|
"@budibase/pro@1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.19-alpha.0.tgz#26a3a1834add9f2b87d1bb9a04708c72cd2eea94"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.2.tgz#fdcb50f1f4da59dab2c7b0095bb37b684bf50c78"
|
||||||
integrity sha512-7g8LlKsrCA0iqsghCMppRFNUb+ytdsK4ZageOgzkFhtNxDT5nqYeERDN+fFd4XhqOCsiUebC1bhkRFHapKfLZA==
|
integrity sha512-yj3nQ0dn+qlSxl3LEnzaizhBPQxKZqcmgi1GdLMh5bwd0xXquD2f7pXMSY0/ygJ7+lZtTlqqu0EG4LNJh/aeRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.3.19-alpha.0"
|
"@budibase/backend-core" "1.4.2"
|
||||||
"@budibase/types" "1.3.19-alpha.0"
|
"@budibase/types" "1.4.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.0":
|
"@budibase/types@1.4.2", "@budibase/types@^1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.19-alpha.0.tgz#402801d1ea5b2e2b82a45a37f06bab28eee8ccb6"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.2.tgz#5f5f7c5dabc5c3b1cca8325693f4257233fd2fa4"
|
||||||
integrity sha512-SKp5cKMar+SUryBwBcdP3zcclCe+48jjMseCNNmjG7ZEkrz0SPWsFE6FJTFfG/PbIgq8DPkvrrCXPY7ZlExW/g==
|
integrity sha512-YeNQ7HzYiltn/YNZNdrX0lkxLJ6G9fTxfiQEPiDeFJnhZLZWJf8PliopGTj3t2IALFzIK9j1PuONexlj2yfcOw==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -13,8 +13,8 @@
|
||||||
},
|
},
|
||||||
"jest": {},
|
"jest": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "14.18.20",
|
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
|
"@types/node": "14.18.20",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.3.19-alpha.0",
|
"version": "1.4.2",
|
||||||
"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.0",
|
"@budibase/backend-core": "^1.4.2",
|
||||||
"@budibase/pro": "1.3.19-alpha.0",
|
"@budibase/pro": "1.4.2",
|
||||||
"@budibase/string-templates": "1.3.19-alpha.0",
|
"@budibase/string-templates": "^1.4.2",
|
||||||
"@budibase/types": "1.3.19-alpha.0",
|
"@budibase/types": "^1.4.2",
|
||||||
"@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",
|
||||||
|
|
|
@ -16,6 +16,11 @@ const { newid } = require("@budibase/backend-core/utils")
|
||||||
const { users } = require("../../../sdk")
|
const { users } = require("../../../sdk")
|
||||||
const { Cookies } = require("@budibase/backend-core/constants")
|
const { Cookies } = require("@budibase/backend-core/constants")
|
||||||
const { events, featureFlags } = require("@budibase/backend-core")
|
const { events, featureFlags } = require("@budibase/backend-core")
|
||||||
|
const env = require("../../../environment")
|
||||||
|
|
||||||
|
function newTestApiKey() {
|
||||||
|
return env.ENCRYPTED_TEST_PUBLIC_API_KEY
|
||||||
|
}
|
||||||
|
|
||||||
function newApiKey() {
|
function newApiKey() {
|
||||||
return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
|
return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
|
||||||
|
@ -29,15 +34,25 @@ function cleanupDevInfo(info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateAPIKey = async ctx => {
|
exports.generateAPIKey = async ctx => {
|
||||||
|
let userId
|
||||||
|
let apiKey
|
||||||
|
if (env.isTest() && ctx.request.body.userId) {
|
||||||
|
userId = ctx.request.body.userId
|
||||||
|
apiKey = newTestApiKey()
|
||||||
|
} else {
|
||||||
|
userId = ctx.user._id
|
||||||
|
apiKey = newApiKey()
|
||||||
|
}
|
||||||
|
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const id = generateDevInfoID(ctx.user._id)
|
const id = generateDevInfoID(userId)
|
||||||
let devInfo
|
let devInfo
|
||||||
try {
|
try {
|
||||||
devInfo = await db.get(id)
|
devInfo = await db.get(id)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
devInfo = { _id: id, userId: ctx.user._id }
|
devInfo = { _id: id, userId }
|
||||||
}
|
}
|
||||||
devInfo.apiKey = await newApiKey()
|
devInfo.apiKey = await apiKey
|
||||||
await db.put(devInfo)
|
await db.put(devInfo)
|
||||||
ctx.body = cleanupDevInfo(devInfo)
|
ctx.body = cleanupDevInfo(devInfo)
|
||||||
}
|
}
|
||||||
|
@ -80,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 => {
|
||||||
|
@ -117,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,
|
||||||
|
@ -128,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 = {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ const env = {
|
||||||
// other
|
// other
|
||||||
CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600,
|
CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600,
|
||||||
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
||||||
|
ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY,
|
||||||
_set(key: any, value: any) {
|
_set(key: any, value: any) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
module.exports[key] = value
|
module.exports[key] = value
|
||||||
|
|
|
@ -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.0":
|
"@budibase/backend-core@1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.19-alpha.0.tgz#e9785994485b16eb0c2f082fe5803c690199a397"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.2.tgz#9c0a40f5c4a1693fd24048f6f130547b0f08e76e"
|
||||||
integrity sha512-avYT4DLDG3GMDNOKHgbNRJIqzHimdhSxjiifbVnMfizLjWL94eAqVgRA4kOiWkEuuH6Wlc5dmI359dJvsWKErw==
|
integrity sha512-htXj7V5mLdf+IqATBlgjABknbT6n/5qRwArlsawYYZzDRTod3rK+W7dIGHDr0JOPJJ8272SuAcJbF80gd65Z+w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "1.3.19-alpha.0"
|
"@budibase/types" "^1.4.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.0":
|
"@budibase/pro@1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.19-alpha.0.tgz#26a3a1834add9f2b87d1bb9a04708c72cd2eea94"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.2.tgz#fdcb50f1f4da59dab2c7b0095bb37b684bf50c78"
|
||||||
integrity sha512-7g8LlKsrCA0iqsghCMppRFNUb+ytdsK4ZageOgzkFhtNxDT5nqYeERDN+fFd4XhqOCsiUebC1bhkRFHapKfLZA==
|
integrity sha512-yj3nQ0dn+qlSxl3LEnzaizhBPQxKZqcmgi1GdLMh5bwd0xXquD2f7pXMSY0/ygJ7+lZtTlqqu0EG4LNJh/aeRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.3.19-alpha.0"
|
"@budibase/backend-core" "1.4.2"
|
||||||
"@budibase/types" "1.3.19-alpha.0"
|
"@budibase/types" "1.4.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.0":
|
"@budibase/types@1.4.2", "@budibase/types@^1.4.2":
|
||||||
version "1.3.19-alpha.0"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.19-alpha.0.tgz#402801d1ea5b2e2b82a45a37f06bab28eee8ccb6"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.2.tgz#5f5f7c5dabc5c3b1cca8325693f4257233fd2fa4"
|
||||||
integrity sha512-SKp5cKMar+SUryBwBcdP3zcclCe+48jjMseCNNmjG7ZEkrz0SPWsFE6FJTFfG/PbIgq8DPkvrrCXPY7ZlExW/g==
|
integrity sha512-YeNQ7HzYiltn/YNZNdrX0lkxLJ6G9fTxfiQEPiDeFJnhZLZWJf8PliopGTj3t2IALFzIK9j1PuONexlj2yfcOw==
|
||||||
|
|
||||||
"@cspotcode/source-map-consumer@0.8.0":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
BB_ADMIN_USER_EMAIL=qa@budibase.com
|
||||||
|
BB_ADMIN_USER_PASSWORD=budibase
|
||||||
|
ENCRYPTED_TEST_PUBLIC_API_KEY=a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c643bb726f
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
watchtower-hook.json
|
||||||
|
dist/
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"name": "@budibase/qa-core",
|
||||||
|
"email": "hi@budibase.com",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "index.js",
|
||||||
|
"description": "Budibase Integration Test Suite",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Budibase/budibase.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest --runInBand",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:debug": "DEBUG=1 jest",
|
||||||
|
"api:server:setup": "env-cmd ts-node ../packages/builder/cypress/ts/setup.ts",
|
||||||
|
"api:server:setup:ci": "env-cmd node ../packages/builder/cypress/setup.js",
|
||||||
|
"api:test:ci": "start-server-and-test api:server:setup:ci http://localhost:4100/builder test",
|
||||||
|
"api:test": "start-server-and-test api:server:setup http://localhost:4100/builder test"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"@budibase/types": "<rootDir>/../packages/types/src",
|
||||||
|
"@budibase/server": "<rootDir>/../packages/server/src"
|
||||||
|
},
|
||||||
|
"setupFiles": [
|
||||||
|
"./scripts/jestSetup.js"
|
||||||
|
],
|
||||||
|
"setupFilesAfterEnv": [
|
||||||
|
"./src/jest.extends.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@budibase/types": "1.3.4",
|
||||||
|
"@types/jest": "29.0.0",
|
||||||
|
"@types/node-fetch": "2.6.2",
|
||||||
|
"chance": "1.1.8",
|
||||||
|
"env-cmd": "^10.1.0",
|
||||||
|
"jest": "28.0.2",
|
||||||
|
"prettier": "2.7.1",
|
||||||
|
"start-server-and-test": "1.14.0",
|
||||||
|
"timekeeper": "2.2.0",
|
||||||
|
"ts-jest": "28.0.8",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"tsconfig-paths": "4.1.0",
|
||||||
|
"typescript": "4.7.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
const env = require("../src/environment")
|
||||||
|
|
||||||
|
env._set("BUDIBASE_SERVER_URL", "http://localhost:4100")
|
||||||
|
env._set(
|
||||||
|
"BUDIBASE_PUBLIC_API_KEY",
|
||||||
|
"a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c643bb726f"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||||
|
// use tk.reset() to use real dates in individual tests
|
||||||
|
const MOCK_DATE = new Date("2020-01-01T00:00:00.000Z")
|
||||||
|
const tk = require("timekeeper")
|
||||||
|
tk.freeze(MOCK_DATE)
|
||||||
|
|
||||||
|
if (!process.env.DEBUG) {
|
||||||
|
global.console.log = jest.fn() // console.log are ignored in tests
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import env from "../../../environment"
|
||||||
|
import fetch, { HeadersInit } from "node-fetch"
|
||||||
|
|
||||||
|
type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
|
||||||
|
|
||||||
|
interface ApiOptions {
|
||||||
|
method?: APIMethod
|
||||||
|
body?: object
|
||||||
|
headers?: HeadersInit | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicAPIClient {
|
||||||
|
host: string
|
||||||
|
apiKey: string
|
||||||
|
appId?: string
|
||||||
|
|
||||||
|
constructor(appId?: string) {
|
||||||
|
if (!env.BUDIBASE_PUBLIC_API_KEY || !env.BUDIBASE_SERVER_URL) {
|
||||||
|
throw new Error(
|
||||||
|
"Must set BUDIBASE_PUBLIC_API_KEY and BUDIBASE_SERVER_URL env vars"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.host = `${env.BUDIBASE_SERVER_URL}/api/public/v1`
|
||||||
|
this.apiKey = env.BUDIBASE_PUBLIC_API_KEY
|
||||||
|
this.appId = appId
|
||||||
|
}
|
||||||
|
|
||||||
|
apiCall =
|
||||||
|
(method: APIMethod) =>
|
||||||
|
async (url = "", options: ApiOptions = {}) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify(options.body),
|
||||||
|
headers: {
|
||||||
|
"x-budibase-api-key": this.apiKey,
|
||||||
|
"x-budibase-app-id": this.appId,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const response = await fetch(`${this.host}${url}`, requestOptions)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.error(response)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
post = this.apiCall("POST")
|
||||||
|
get = this.apiCall("GET")
|
||||||
|
patch = this.apiCall("PATCH")
|
||||||
|
del = this.apiCall("DELETE")
|
||||||
|
put = this.apiCall("PUT")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublicAPIClient
|
|
@ -0,0 +1,49 @@
|
||||||
|
import PublicAPIClient from "./PublicAPIClient"
|
||||||
|
import {
|
||||||
|
Application,
|
||||||
|
SearchInputParams,
|
||||||
|
CreateApplicationParams,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import generateApp from "../fixtures/applications"
|
||||||
|
|
||||||
|
export default class AppApi {
|
||||||
|
api: PublicAPIClient
|
||||||
|
|
||||||
|
constructor(apiClient: PublicAPIClient) {
|
||||||
|
this.api = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async seed(): Promise<[Response, Application]> {
|
||||||
|
return this.create(generateApp())
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
body: CreateApplicationParams
|
||||||
|
): Promise<[Response, Application]> {
|
||||||
|
const response = await this.api.post(`/applications`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(id: string): Promise<[Response, Application]> {
|
||||||
|
const response = await this.api.get(`/applications/${id}`)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(body: SearchInputParams): Promise<[Response, [Application]]> {
|
||||||
|
const response = await this.api.post(`/applications/search`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
body: Application
|
||||||
|
): Promise<[Response, Application]> {
|
||||||
|
const response = await this.api.put(`/applications/${id}`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
const Chance = require("chance")
|
||||||
|
|
||||||
|
export default new Chance()
|
|
@ -0,0 +1,27 @@
|
||||||
|
import PublicAPIClient from "./PublicAPIClient"
|
||||||
|
import ApplicationApi from "./applications"
|
||||||
|
import TableApi from "./tables"
|
||||||
|
import UserApi from "./users"
|
||||||
|
import RowApi from "./rows"
|
||||||
|
|
||||||
|
export default class TestConfiguration<T> {
|
||||||
|
applications: ApplicationApi
|
||||||
|
users: UserApi
|
||||||
|
tables: TableApi
|
||||||
|
rows: RowApi
|
||||||
|
context: T
|
||||||
|
|
||||||
|
constructor(apiClient: PublicAPIClient) {
|
||||||
|
this.applications = new ApplicationApi(apiClient)
|
||||||
|
this.users = new UserApi(apiClient)
|
||||||
|
this.tables = new TableApi(apiClient)
|
||||||
|
this.rows = new RowApi(apiClient)
|
||||||
|
this.context = <T>{}
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeAll() {}
|
||||||
|
|
||||||
|
async afterAll() {
|
||||||
|
this.context = <T>{}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import PublicAPIClient from "./PublicAPIClient"
|
||||||
|
import {
|
||||||
|
CreateRowParams,
|
||||||
|
Row,
|
||||||
|
SearchInputParams,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { HeadersInit, Response } from "node-fetch"
|
||||||
|
import { generateRow } from "../fixtures/tables"
|
||||||
|
|
||||||
|
export default class RowApi {
|
||||||
|
api: PublicAPIClient
|
||||||
|
headers?: HeadersInit
|
||||||
|
tableId?: string
|
||||||
|
|
||||||
|
constructor(apiClient: PublicAPIClient) {
|
||||||
|
this.api = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async seed(tableId: string) {
|
||||||
|
return this.create(generateRow({ tableId }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(body: CreateRowParams): Promise<[Response, Row]> {
|
||||||
|
const response = await this.api.post(`/tables/${this.tableId}/rows`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(id: string): Promise<[Response, Row]> {
|
||||||
|
const response = await this.api.get(`/tables/${this.tableId}/rows/${id}`)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(body: SearchInputParams): Promise<[Response, [Row]]> {
|
||||||
|
const response = await this.api.post(
|
||||||
|
`/tables/${this.tableId}/rows/search`,
|
||||||
|
{ body }
|
||||||
|
)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, body: Row): Promise<[Response, Row]> {
|
||||||
|
const response = await this.api.put(`/tables/${this.tableId}/rows/${id}`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import PublicAPIClient from "./PublicAPIClient"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
SearchInputParams,
|
||||||
|
CreateTableParams,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { HeadersInit, Response } from "node-fetch"
|
||||||
|
import { generateTable } from "../fixtures/tables"
|
||||||
|
|
||||||
|
export default class TableApi {
|
||||||
|
api: PublicAPIClient
|
||||||
|
headers?: HeadersInit
|
||||||
|
|
||||||
|
constructor(apiClient: PublicAPIClient) {
|
||||||
|
this.api = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async seed() {
|
||||||
|
return this.create(generateTable())
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(body: CreateTableParams): Promise<[Response, Table]> {
|
||||||
|
const response = await this.api.post(`/tables`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(id: string): Promise<[Response, Table]> {
|
||||||
|
const response = await this.api.get(`/tables/${id}`)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(body: SearchInputParams): Promise<[Response, [Table]]> {
|
||||||
|
const response = await this.api.post(`/tables/search`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, body: Table): Promise<[Response, Table]> {
|
||||||
|
const response = await this.api.put(`/tables/${id}`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import PublicAPIClient from "./PublicAPIClient"
|
||||||
|
import {
|
||||||
|
CreateUserParams,
|
||||||
|
SearchInputParams,
|
||||||
|
User,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import generateUser from "../fixtures/users"
|
||||||
|
|
||||||
|
export default class UserApi {
|
||||||
|
api: PublicAPIClient
|
||||||
|
|
||||||
|
constructor(apiClient: PublicAPIClient) {
|
||||||
|
this.api = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async seed() {
|
||||||
|
return this.create(generateUser())
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(body: CreateUserParams): Promise<[Response, User]> {
|
||||||
|
const response = await this.api.post(`/users`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(id: string): Promise<[Response, User]> {
|
||||||
|
const response = await this.api.get(`/users/${id}`)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(body: SearchInputParams): Promise<[Response, [User]]> {
|
||||||
|
const response = await this.api.post(`/users/search`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, body: User): Promise<[Response, User]> {
|
||||||
|
const response = await this.api.put(`/users/${id}`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json.data]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import generator from "../TestConfiguration/generator"
|
||||||
|
import {
|
||||||
|
Application,
|
||||||
|
CreateApplicationParams,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
|
||||||
|
const generate = (
|
||||||
|
overrides: Partial<Application> = {}
|
||||||
|
): CreateApplicationParams => ({
|
||||||
|
name: generator.word(),
|
||||||
|
url: `/${generator.word()}`,
|
||||||
|
...overrides,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default generate
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {
|
||||||
|
CreateRowParams,
|
||||||
|
CreateTableParams,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import generator from "../TestConfiguration/generator"
|
||||||
|
|
||||||
|
export const generateTable = (
|
||||||
|
overrides: Partial<Table> = {}
|
||||||
|
): CreateTableParams => ({
|
||||||
|
name: generator.word(),
|
||||||
|
primaryDisplay: "testColumn",
|
||||||
|
schema: {
|
||||||
|
"Auto ID": {
|
||||||
|
autocolumn: true,
|
||||||
|
name: "Auto ID",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
"Created At": {
|
||||||
|
autocolumn: true,
|
||||||
|
name: "Created At",
|
||||||
|
type: "datetime",
|
||||||
|
},
|
||||||
|
"Created By": {
|
||||||
|
autocolumn: true,
|
||||||
|
fieldName: generator.word(),
|
||||||
|
name: "Created By",
|
||||||
|
relationshipType: "many-to-many",
|
||||||
|
tableId: "ta_users",
|
||||||
|
type: "link",
|
||||||
|
},
|
||||||
|
testColumn: {
|
||||||
|
name: "testColumn",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
"Updated At": {
|
||||||
|
autocolumn: true,
|
||||||
|
name: "Updated At",
|
||||||
|
type: "datetime",
|
||||||
|
},
|
||||||
|
"Updated By": {
|
||||||
|
autocolumn: true,
|
||||||
|
fieldName: generator.word(),
|
||||||
|
name: "Updated By",
|
||||||
|
relationshipType: "many-to-many",
|
||||||
|
tableId: "ta_users",
|
||||||
|
type: "link",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const generateRow = (overrides: Partial<Row> = {}): CreateRowParams => ({
|
||||||
|
type: "row",
|
||||||
|
tableId: "seed_table",
|
||||||
|
testColumn: generator.string({ length: 32, alpha: true, numeric: true }),
|
||||||
|
relationship: [generator.string({ length: 32, alpha: true, numeric: true })],
|
||||||
|
...overrides,
|
||||||
|
})
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
CreateUserParams,
|
||||||
|
User,
|
||||||
|
} from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import generator from "../TestConfiguration/generator"
|
||||||
|
|
||||||
|
const generate = (overrides: Partial<User> = {}): CreateUserParams => ({
|
||||||
|
email: generator.email(),
|
||||||
|
roles: {
|
||||||
|
[generator.string({ length: 32, alpha: true, numeric: true })]:
|
||||||
|
generator.word(),
|
||||||
|
},
|
||||||
|
password: generator.word(),
|
||||||
|
status: "active",
|
||||||
|
forceResetPassword: generator.bool(),
|
||||||
|
builder: {
|
||||||
|
global: generator.bool(),
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
global: generator.bool(),
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default generate
|
|
@ -0,0 +1,10 @@
|
||||||
|
const env = {
|
||||||
|
BUDIBASE_SERVER_URL: process.env.BUDIBASE_SERVER_URL,
|
||||||
|
BUDIBASE_PUBLIC_API_KEY: process.env.BUDIBASE_PUBLIC_API_KEY,
|
||||||
|
_set(key: any, value: any) {
|
||||||
|
process.env[key] = value
|
||||||
|
module.exports[key] = value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export = env
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
|
||||||
|
// boilerplate to allow TS updates to the global scope
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace jest {
|
||||||
|
interface Matchers<R> {
|
||||||
|
toHaveStatusCode(code: number): R
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect extensions
|
||||||
|
expect.extend({
|
||||||
|
toHaveStatusCode(received: Response, code: number) {
|
||||||
|
const pass = received.status === code
|
||||||
|
return {
|
||||||
|
message: () => `expected ${received.status} to match status code ${code}`,
|
||||||
|
pass,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,50 @@
|
||||||
|
import TestConfiguration from "../../../config/public-api/TestConfiguration"
|
||||||
|
import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient"
|
||||||
|
import generateApp from "../../../config/public-api/fixtures/applications"
|
||||||
|
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
|
||||||
|
describe("Public API - /applications endpoints", () => {
|
||||||
|
const api = new PublicAPIClient()
|
||||||
|
const config = new TestConfiguration<Application>(api)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
const [response, app] = await config.applications.seed()
|
||||||
|
config.context = app
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Create an application", async () => {
|
||||||
|
const [response, app] = await config.applications.create(generateApp())
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(app._id).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Search applications", async () => {
|
||||||
|
const [response, apps] = await config.applications.search({
|
||||||
|
name: config.context.name,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(apps[0]).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("GET - Retrieve an application", async () => {
|
||||||
|
const [response, app] = await config.applications.read(config.context._id)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(app).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("PUT - update an application", async () => {
|
||||||
|
config.context.name = "UpdatedName"
|
||||||
|
const [response, app] = await config.applications.update(
|
||||||
|
config.context._id,
|
||||||
|
config.context
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(app.updatedAt).not.toEqual(config.context.updatedAt)
|
||||||
|
expect(app.name).toEqual(config.context.name)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Row } from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { generateRow } from "../../../config/public-api/fixtures/tables"
|
||||||
|
import TestConfiguration from "../../../config/public-api/TestConfiguration"
|
||||||
|
import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient"
|
||||||
|
|
||||||
|
describe("Public API - /rows endpoints", () => {
|
||||||
|
let api = new PublicAPIClient()
|
||||||
|
|
||||||
|
const config = new TestConfiguration<Row>(api)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
const [aResp, app] = await config.applications.seed()
|
||||||
|
|
||||||
|
config.tables.api.appId = app._id
|
||||||
|
config.rows.api.appId = app._id
|
||||||
|
|
||||||
|
const [tResp, table] = await config.tables.seed()
|
||||||
|
config.rows.tableId = table._id
|
||||||
|
|
||||||
|
const [rResp, row] = await config.rows.seed(table._id)
|
||||||
|
config.context = row
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Create a row", async () => {
|
||||||
|
const [response, row] = await config.rows.create(generateRow())
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(row._id).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Search rows", async () => {
|
||||||
|
const [response, rows] = await config.rows.search({
|
||||||
|
query: {
|
||||||
|
string: {
|
||||||
|
testColumn: config.context.testColumn as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(rows[0]._id).toEqual(config.context._id)
|
||||||
|
expect(rows[0].tableId).toEqual(config.context.tableId)
|
||||||
|
expect(rows[0].testColumn).toEqual(config.context.testColumn)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("GET - Retrieve a row", async () => {
|
||||||
|
const [response, row] = await config.rows.read(config.context._id)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(row._id).toEqual(config.context._id)
|
||||||
|
expect(row.tableId).toEqual(config.context.tableId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("PUT - update a row", async () => {
|
||||||
|
config.context.testColumn = "UpdatedName"
|
||||||
|
const [response, row] = await config.rows.update(
|
||||||
|
config.context._id,
|
||||||
|
config.context
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(row.testColumn).toEqual(config.context.testColumn)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Table } from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
import { generateTable } from "../../../config/public-api/fixtures/tables"
|
||||||
|
import TestConfiguration from "../../../config/public-api/TestConfiguration"
|
||||||
|
import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient"
|
||||||
|
|
||||||
|
describe("Public API - /tables endpoints", () => {
|
||||||
|
let api = new PublicAPIClient()
|
||||||
|
const config = new TestConfiguration<Table>(api)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
const [appResp, app] = await config.applications.seed()
|
||||||
|
config.tables.api.appId = app._id
|
||||||
|
|
||||||
|
const [tableResp, table] = await config.tables.seed()
|
||||||
|
config.context = table
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Create a table", async () => {
|
||||||
|
const [response, table] = await config.tables.create(generateTable())
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(table._id).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Search tables", async () => {
|
||||||
|
const [response, tables] = await config.tables.search({
|
||||||
|
name: config.context.name,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(tables[0]).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("GET - Retrieve a table", async () => {
|
||||||
|
const [response, table] = await config.tables.read(config.context._id)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(table).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("PUT - update a table", async () => {
|
||||||
|
config.context.name = "updatedName"
|
||||||
|
const [response, table] = await config.tables.update(
|
||||||
|
config.context._id,
|
||||||
|
config.context
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(table).toEqual(config.context)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,49 @@
|
||||||
|
import TestConfiguration from "../../../config/public-api/TestConfiguration"
|
||||||
|
import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient"
|
||||||
|
import generateUser from "../../../config/public-api/fixtures/users"
|
||||||
|
import { User } from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
|
||||||
|
describe("Public API - /users endpoints", () => {
|
||||||
|
const api = new PublicAPIClient()
|
||||||
|
const config = new TestConfiguration<User>(api)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
const [_, user] = await config.users.seed()
|
||||||
|
config.context = user
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Create a user", async () => {
|
||||||
|
const [response, user] = await config.users.create(generateUser())
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(user._id).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Search users", async () => {
|
||||||
|
const [response, users] = await config.users.search({
|
||||||
|
name: config.context.email,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(users[0]).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("GET - Retrieve a user", async () => {
|
||||||
|
const [response, user] = await config.users.read(config.context._id)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(user).toEqual(config.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("PUT - update a user", async () => {
|
||||||
|
config.context.firstName = "Updated First Name"
|
||||||
|
const [response, user] = await config.users.update(
|
||||||
|
config.context._id,
|
||||||
|
config.context
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(user).toEqual(config.context)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"incremental": true,
|
||||||
|
"types": ["node", "jest"],
|
||||||
|
"outDir": "dist",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"paths": {
|
||||||
|
"@budibase/types": ["../packages/types/src"],
|
||||||
|
"@budibase/server/*": ["../packages/server/src/*"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"require": ["tsconfig-paths/register"]
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{ "path": "../packages/types" },
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue